1. 项目概述:基于协同过滤的短视频推荐系统实战
去年接手一个短视频平台的重构项目时,最让我头疼的就是推荐系统效果不佳。用户平均观看时长不足90秒,30%的用户在注册后三天内流失。经过两周的数据分析,我们发现核心问题出在推荐算法上——老系统仅基于热门视频做简单排序,完全忽视了个性化需求。这就是我们决定采用协同过滤算法重构整个推荐模块的起因。
这个采用Python+Django+Vue3技术栈构建的系统,核心目标是通过用户行为数据实现真正的个性化推荐。在三个月内,我们实现了推荐准确率提升40%,用户留存率提高25%的显著效果。下面我将从架构设计、算法实现到性能优化,完整还原这个千万级用户平台的推荐系统实战经验。
2. 技术架构设计解析
2.1 整体技术栈选型
后端架构决策过程:
最初在Flask和Django之间犹豫时,我们做了个简单的压力测试:使用相同硬件配置,Django在并发1000请求时的响应时间为230ms,而Flask达到380ms。考虑到Django自带的ORM、Admin后台以及更完善的安全机制(如CSRF防护、XSS过滤),最终选择了Django作为核心框架。
数据库选型时,对比了MySQL和PostgreSQL在用户行为数据存储上的表现:
- MySQL 8.0:写入速度更快(约15%优势),适合高频行为日志记录
- PostgreSQL 12:在复杂查询(如多维度用户相似度计算)时快20%
最终采用混合方案:用户基础数据用PostgreSQL,行为日志用MySQL分库分表。
2.2 前端架构设计要点
Vue3的组合式API让我们能更灵活地组织推荐逻辑代码。这个代码片段展示了如何在前端实现推荐结果的实时权重调整:
javascript复制// 在Pinia store中管理推荐状态
export const useRecStore = defineStore('recommend', () => {
const recList = ref([])
const loading = ref(false)
// 根据用户实时交互调整推荐权重
const adjustWeight = (videoId, actionType) => {
const index = recList.value.findIndex(item => item.id === videoId)
if (index !== -1) {
switch(actionType) {
case 'like':
recList.value[index].weight *= 1.3
break
case 'skip':
recList.value[index].weight *= 0.7
break
case 'share':
recList.value[index].weight *= 1.5
}
// 重新排序
recList.value.sort((a,b) => b.weight - a.weight)
}
}
return { recList, loading, adjustWeight }
})
2.3 缓存策略设计
推荐结果缓存采用三级策略:
- 用户个性化推荐:Redis缓存,TTL 30分钟
- 热门视频列表:Redis缓存,TTL 5分钟
- 冷启动推荐:本地内存缓存,TTL 1小时
实测这个方案使API响应时间从平均320ms降低到89ms。关键配置如下:
python复制CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor",
"SOCKET_CONNECT_TIMEOUT": 5,
"SOCKET_TIMEOUT": 5,
}
}
}
3. 协同过滤算法深度实现
3.1 用户相似度计算优化
原始的用户协同过滤存在两个致命问题:
- 热门视频干扰:所有用户都可能看过热门视频,导致相似度失真
- 计算复杂度高:用户数N的平方级复杂度
我们的解决方案:
python复制def improved_cosine_sim(user1, user2):
# 获取共同观看视频
common_videos = set(user1.views.keys()) & set(user2.views.keys())
# 引入流行度惩罚因子
pop_penalty = {
vid: 1 / math.log(1 + global_popularity[vid])
for vid in common_videos
}
# 加权余弦相似度
dot_product = sum(
user1.views[vid] * user2.views[vid] * pop_penalty[vid]
for vid in common_videos
)
norm1 = math.sqrt(sum(
(user1.views[vid] * pop_penalty[vid])**2
for vid in common_videos
))
norm2 = math.sqrt(sum(
(user2.views[vid] * pop_penalty[vid])**2
for vid in common_videos
))
return dot_product / (norm1 * norm2) if norm1 and norm2 else 0
这个改进使算法在保持85%召回率的同时,将误推荐热门视频的比例从38%降到12%。
3.2 混合推荐策略
单纯User-Based CF在新用户场景下效果很差。我们设计了混合推荐流程:
mermaid复制graph TD
A[新用户?] -->|是| B[热门+随机推荐]
A -->|否| C[计算用户相似度]
C --> D[获取TopN相似用户]
D --> E[生成推荐候选集]
E --> F[基于物品相似度过滤]
F --> G[时效性加权]
G --> H[多样性控制]
H --> I[最终推荐列表]
具体实现时的权重分配:
- 新用户期(前3天):热门60% + 随机40%
- 成长期(4-14天):User-CF 70% + Item-CF 30%
- 成熟期(>14天):User-CF 50% + Item-CF 30% + 时效性20%
3.3 矩阵分解优化
使用Surprise库实现SVD++:
python复制from surprise import SVDpp, Dataset
from surprise.model_selection import train_test_split
def train_svdpp():
# 加载用户行为数据
data = Dataset.load_from_df(ratings_df[['user_id', 'video_id', 'weight']],
reader=Reader(rating_scale=(0, 5)))
trainset, testset = train_test_split(data, test_size=0.2)
algo = SVDpp(n_factors=50, n_epochs=20, lr_all=0.005, reg_all=0.02)
algo.fit(trainset)
# 测试集评估
predictions = algo.test(testset)
accuracy.rmse(predictions)
return algo
关键参数选择经验:
- n_factors:50-100之间效果最佳,超过100容易过拟合
- lr_all:0.003-0.01,需要配合早停机制
- reg_all:通常0.01-0.1,用网格搜索确定
4. 工程实现关键问题
4.1 冷启动解决方案
我们设计了三级冷启动策略:
- 基于用户注册信息(选择兴趣标签)
- 基于设备信息(地理位置、机型)
- 基于社交关系(通讯录好友)
核心代码片段:
python复制def cold_start_recommend(user):
# 第一级:兴趣标签
if user.tags:
recs = Video.objects.filter(
tags__overlap=user.tags
).order_by('-popularity')[:20]
# 第二级:地理位置
elif user.location:
loc_recs = Video.objects.filter(
location=user.location
).order_by('-create_time')[:10]
hot_recs = get_hot_videos(limit=10)
recs = list(loc_recs) + list(hot_recs)
# 第三级:完全冷启动
else:
recs = get_hot_videos(limit=15) + get_random_videos(limit=5)
return recs
4.2 实时反馈系统
用户行为实时处理流程:
- 前端埋点收集行为数据
- WebSocket实时推送至Kafka
- Flink实时处理
- 更新Redis推荐权重
关键Flink作业配置:
java复制DataStream<UserAction> actions = env
.addSource(new KafkaSource<>())
.keyBy(UserAction::getUserId)
.process(new ActionProcessor());
actions.addSink(new RedisSink());
4.3 性能优化实战
数据库优化:
- 用户行为表按用户ID哈希分片
- 建立复合索引:(user_id, action_type, timestamp)
- 定期归档冷数据
算法加速:
- 相似度计算改用Faiss库
- 预计算用户邻居图,每小时更新
- 异步计算推荐结果
5. 效果评估与调优
5.1 核心指标对比
| 指标 | 旧系统 | 新系统 | 提升幅度 |
|---|---|---|---|
| CTR | 3.2% | 6.7% | 109% |
| 平均观看时长 | 87s | 142s | 63% |
| 次日留存 | 52% | 68% | 31% |
| 7日留存 | 28% | 41% | 46% |
5.2 AB测试策略
我们设计了分层AB测试框架:
- 按用户ID哈希分桶(100个桶)
- 不同桶分配不同算法组合
- 数据统计使用T+1模式
测试发现:
- 在女性用户中,加入社交关系的推荐CTR提升27%
- 青少年群体对时效性强的视频更敏感
- 30岁以上用户更喜欢Item-Based推荐
5.3 常见问题排查
问题1:推荐结果过于集中
- 原因:相似度计算未考虑多样性
- 解决:在排序公式中加入多样性惩罚项
问题2:新视频曝光不足
- 原因:冷启动视频没有初始权重
- 解决:加入曝光补偿机制,前100次展示加权
问题3:深夜推荐效果差
- 原因:未区分时段特征
- 解决:建立24小时时段模型,每个时段单独训练
6. 部署与运维实践
6.1 服务器配置建议
最小生产环境配置:
- API服务器:4核8G × 3台(负载均衡)
- Redis集群:8G内存 × 2台(主从)
- MySQL集群:16核32G × 2台(主从)
- 算法服务器:GPU实例(训练用)
6.2 监控指标设置
必备监控项:
- 推荐API响应时间(P99 < 300ms)
- 算法更新延迟(<5分钟)
- 用户行为丢失率(<0.1%)
- 缓存命中率(>85%)
6.3 升级迭代路径
我们的演进路线:
- v1.0:基础协同过滤
- v1.5:加入矩阵分解
- v2.0:实时推荐
- v2.5:多目标优化(观看时长+互动率)
- v3.0:图神经网络引入
在实现这个系统的过程中,最深刻的体会是:推荐系统不是算法越复杂越好,关键要建立完整的数据闭环和快速的迭代机制。我们曾花费两周实现复杂的深度学习模型,效果却不如简单调整特征权重的老模型。后来形成的原则是:任何算法改进必须通过AB测试验证,且每次只测试一个变量。