这个项目构建了一个完整的餐厅推荐系统,核心采用基于用户的协同过滤算法。系统后端使用Python Flask框架开发,前端采用Vue.js,数据库选用MySQL。推荐算法通过分析用户历史评分数据,计算用户相似度,为每位用户生成个性化餐厅推荐列表。
在实际应用中,我发现这类系统最关键的挑战在于处理数据稀疏性和冷启动问题。当新用户或新餐厅加入系统时,由于缺乏足够的评分数据,传统协同过滤算法效果会大打折扣。我们的解决方案是结合基于内容的推荐方法作为补充。
系统采用典型的三层架构:
这种分层设计使系统各组件职责明确,便于后期扩展和维护。我在实际部署中发现,将用户会话数据和频繁访问的热门餐厅列表放在Redis中,能显著提升系统响应速度。
系统包含以下关键模块:
提示:JWT认证需要注意设置合理的过期时间,我们项目中设为24小时,既保证安全性又避免频繁重新登录
首先需要构建用户-餐厅评分矩阵。这个矩阵通常非常稀疏,因为大多数用户只对少量餐厅有过评分。我们使用Pandas的稀疏矩阵存储方式,大幅减少内存占用。
python复制import pandas as pd
from scipy.sparse import csr_matrix
# 从数据库加载评分数据
ratings = pd.read_sql('SELECT user_id, restaurant_id, rating FROM user_ratings', con=db_engine)
# 创建稀疏评分矩阵
rating_matrix = ratings.pivot(index='user_id',
columns='restaurant_id',
values='rating').fillna(0)
sparse_ratings = csr_matrix(rating_matrix.values)
我们实现了两种相似度计算方法:
python复制from sklearn.metrics.pairwise import cosine_similarity
def calculate_user_similarity(ratings_matrix):
"""计算用户相似度矩阵"""
# 使用余弦相似度
similarity = cosine_similarity(ratings_matrix)
np.fill_diagonal(similarity, 0) # 将对角线设为0,避免自我推荐
return similarity
基于相似用户的加权平均评分进行预测:
python复制def predict_ratings(user_id, similarity_matrix, ratings_matrix, k=20):
"""预测用户对未评分餐厅的评分"""
# 获取目标用户的评分向量
user_ratings = ratings_matrix[user_id, :].toarray().flatten()
# 找到最相似的k个用户
similar_users = np.argsort(similarity_matrix[user_id, :])[-k:]
# 计算加权平均评分
weighted_sum = np.zeros(ratings_matrix.shape[1])
similarity_sum = np.zeros(ratings_matrix.shape[1])
for sim_user in similar_users:
similarity = similarity_matrix[user_id, sim_user]
weighted_sum += similarity * ratings_matrix[sim_user, :].toarray().flatten()
similarity_sum += similarity * (ratings_matrix[sim_user, :].toarray().flatten() != 0)
# 避免除以0
similarity_sum[similarity_sum == 0] = 1e-10
predicted_ratings = weighted_sum / similarity_sum
# 将已评分的餐厅预测值设为0
predicted_ratings[user_ratings != 0] = 0
return predicted_ratings
用户相似度矩阵计算开销大,我们将其缓存在Redis中,设置24小时过期时间。当新评分产生时,只更新受影响的部分相似度值,而非全量重算。
python复制import redis
import pickle
redis_client = redis.Redis(host='localhost', port=6379, db=0)
def get_user_similarity(user_id):
"""从缓存获取用户相似度"""
cache_key = f"user_sim:{user_id}"
cached = redis_client.get(cache_key)
if cached:
return pickle.loads(cached)
else:
# 计算并缓存
similarity = calculate_similarity_for_user(user_id)
redis_client.setex(cache_key, 86400, pickle.dumps(similarity))
return similarity
对于新用户,我们采用混合推荐策略:
python复制def handle_cold_start(user_info):
"""处理冷启动用户的推荐"""
if user_info['rating_count'] < 5: # 新用户判定
# 获取同地区热门餐厅
local_popular = get_local_popular(user_info['location'])
# 获取相似人口统计特征用户的偏好
demo_similar = get_demographic_similar(user_info)
# 组合推荐结果
recommendations = combine_recommendations([local_popular, demo_similar])
return recommendations
else:
return None # 不是新用户,走正常推荐流程
我们采用三种指标评估推荐质量:
python复制from sklearn.metrics import mean_squared_error
def evaluate_model(test_ratings, predicted_ratings):
"""评估推荐模型"""
# 计算RMSE
rmse = np.sqrt(mean_squared_error(test_ratings[test_ratings != 0],
predicted_ratings[test_ratings != 0]))
# 计算准确率和召回率
test_positives = set(np.where(test_ratings >= 4)[0]) # 假设评分>=4表示喜欢
pred_positives = set(np.where(predicted_ratings >= 4)[0])
tp = len(test_positives & pred_positives)
precision = tp / len(pred_positives) if len(pred_positives) > 0 else 0
recall = tp / len(test_positives) if len(test_positives) > 0 else 0
# 计算覆盖率
coverage = len(pred_positives) / predicted_ratings.shape[0]
return {'rmse': rmse, 'precision': precision, 'recall': recall, 'coverage': coverage}
通过网格搜索确定最优参数组合:
python复制from sklearn.model_selection import ParameterGrid
def tune_parameters(ratings_matrix):
"""调优相似度计算参数"""
param_grid = {
'similarity': ['cosine', 'pearson'],
'k': [10, 20, 50],
'min_similarity': [0.1, 0.3, 0.5]
}
best_score = float('inf')
best_params = None
for params in ParameterGrid(param_grid):
# 交叉验证评估
scores = cross_validate(ratings_matrix, params)
avg_rmse = np.mean(scores['rmse'])
if avg_rmse < best_score:
best_score = avg_rmse
best_params = params
return best_params
我们使用Gunicorn作为WSGI服务器,Nginx作为反向代理:
bash复制# 启动Gunicorn
gunicorn -w 4 -b 127.0.0.1:8000 app:app
# Nginx配置示例
server {
listen 80;
server_name api.restaurant-recommender.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
使用Prometheus+Grafana监控系统关键指标:
注意:在生产环境中,推荐计算应该异步执行,通过消息队列(如RabbitMQ)将推荐任务与实时API响应分离
经过半年多的实际运营,我们积累了一些宝贵经验:
数据质量至关重要:初期由于部分用户随意评分,导致推荐质量下降。后来我们增加了评分引导机制(如要求用户至少评价5家餐厅后才能获得推荐),显著提升了数据质量。
算法多样性:单一算法难以满足所有场景。我们最终实现了算法路由机制,根据不同用户特征选择最适合的推荐策略:
解释性提升接受度:最初用户对推荐结果信任度低。后来我们增加了推荐理由(如"因为您喜欢A餐厅,与您口味相似的用户也喜欢B餐厅"),点击率提升了40%。
AB测试必不可少:任何算法改进都必须经过严格的AB测试。我们建立了完整的实验框架,确保新算法在部分用户群验证有效后才会全量上线。
这个项目从零开始构建完整的推荐系统,涉及算法研发、系统架构、性能优化等多个方面。最大的收获是认识到推荐系统不仅是算法问题,更是系统工程和用户体验设计的综合课题。