3、经典召回策略(上):基于协同过滤的召回(UserCF、ItemCF)、矩阵分解(SVD、SVD++)
各位同学,今天我们来聊聊推荐系统里最经典、也最“有年头”的召回策略。说实话,虽然现在深度学习大行其道,但协同过滤和矩阵分解这些老家伙,依然是很多工业级系统的基石。我自己的第一个推荐项目,就是从UserCF入手的,踩过的坑那叫一个多。
咱们先理清一个概念:召回阶段要的是什么?是“快”和“广”。你想想看,全量商品可能有上亿个,你不可能让排序模型一个个打分。召回就是先圈定一个几百上千的候选集,把最有可能被用户喜欢的商品捞出来。今天讲的这几种方法,就是干这个活的。
3.1 基于用户的协同过滤(UserCF)
UserCF的核心思想很简单:找到和你兴趣相似的人,把他们喜欢的东西推荐给你。说白了就是“物以类聚,人以群分”。
具体怎么做呢?分三步:
- 计算用户相似度:用用户的历史行为(比如点击、购买)构建向量,然后算余弦相似度或者皮尔逊相关系数。
- 找到最近邻:对每个用户,找出相似度最高的K个用户(比如K=50)。
- 生成推荐列表:把这K个用户喜欢的、但目标用户没见过的商品,按得分排序推荐。
重要公式:用户相似度计算(余弦相似度)
sim(u, v) = (r_u · r_v) / (||r_u|| * ||r_v||)
其中 r_u 和 r_v 是用户 u 和 v 的行为向量。
我在项目中遇到过一个问题:UserCF在用户量大的时候,相似度矩阵的计算量是O(N²)级别的。100万用户,矩阵就是1万亿个元素,根本存不下。怎么办?我当时的做法是离线预计算,每天凌晨跑一次,把Top-K相似用户存到Redis里。线上召回时直接查,毫秒级响应。
避坑指南:我曾经天真地以为用户越多,UserCF效果越好。结果发现,新用户行为稀疏,根本找不到相似用户。这就是所谓的“冷启动”问题。后来我加了一个“热门商品兜底”策略,新用户先推热门,等行为够了再切到个性化。
3.2 基于物品的协同过滤(ItemCF)
ItemCF的思路刚好反过来:找到和用户喜欢的商品相似的其他商品。比如你买了《推荐系统实践》,系统就会推荐《机器学习实战》,因为这两本书经常被一起购买。
ItemCF的计算流程:
- 构建物品共现矩阵:统计两个物品被同一个用户喜欢的次数。
- 计算物品相似度:用共现次数除以物品的流行度,避免热门物品主导。
- 生成推荐:根据用户历史喜欢的物品,找出最相似的N个物品推荐。
重要公式:物品相似度(改进版)
sim(i, j) = count(用户同时喜欢i和j) / sqrt(count(喜欢i) * count(喜欢j))
这个公式加了惩罚项,防止热门物品和所有物品都相似。
嗯,这里要注意:ItemCF和UserCF各有适用场景。我个人的经验是:
- UserCF:适合用户数少、物品数多的场景(比如新闻推荐)。因为用户相似度变化快,能捕捉热点。
- ItemCF:适合物品数少、用户数多的场景(比如电商、视频)。物品相似度相对稳定,可以离线算好。
| 对比维度 | UserCF | ItemCF |
|---|---|---|
| 计算复杂度 | O(N²),用户数N | O(M²),物品数M |
| 实时性 | 差,需要实时算相似用户 | 好,物品相似度可离线 |
| 冷启动 | 新用户困难 | 新物品困难 |
| 推荐解释 | “和你相似的人也喜欢” | “因为你喜欢XX” |
3.3 矩阵分解:SVD
协同过滤虽然直观,但有个硬伤:数据稀疏。用户-物品矩阵里,99%以上的元素都是空的。矩阵分解就是来解决这个问题的。
SVD(奇异值分解)的核心思想:把高维稀疏的用户-物品矩阵,分解成两个低维稠密矩阵的乘积。一个矩阵代表用户的隐向量,另一个代表物品的隐向量。隐向量维度一般取50-200,远小于用户数和物品数。
数学表达
R ≈ U * Σ * V^T
其中R是用户-物品矩阵,U是用户特征矩阵,Σ是奇异值对角矩阵,V是物品特征矩阵。
我建议你记住这个公式,面试经常考。但实际工程中,我们很少直接用标准的SVD,因为:
- 标准SVD要求矩阵是稠密的,而我们的矩阵是稀疏的。
- 填充缺失值(比如填0)会引入大量噪声。
所以工业界更常用的是Funk-SVD,也就是只对已知的评分做分解,忽略缺失值。说白了就是:只学用户和物品的隐向量,让预测值和真实值的误差最小。
注意:SVD分解出来的隐向量没有物理含义。你没法说第3个维度代表“价格敏感度”,第5个维度代表“喜欢动作片”。它只是数学上的最优解。我曾经花了两周时间试图解释每个维度,最后放弃了——效果好就行,别纠结可解释性。
3.4 SVD++:加入隐式反馈
SVD++是SVD的升级版。它解决了一个实际问题:用户的很多行为不是显式评分,而是隐式反馈。比如点击、浏览时长、购买等。这些信息同样有价值。
SVD++的改进点:在用户隐向量的基础上,额外加入一个“隐式反馈偏置项”。公式变成:
SVD++预测公式
r̂_ui = μ + b_u + b_i + q_i^T * (p_u + |N(u)|^(-1/2) * Σ_{j∈N(u)} y_j)
其中:
- μ:全局平均分
- b_u:用户偏置
- b_i:物品偏置
- p_u:用户隐向量
- q_i:物品隐向量
- y_j:隐式反馈的物品隐向量
- N(u):用户u有过隐式反馈的物品集合
你想想看,这个公式多了一个求和项,把用户所有隐式行为都考虑进去了。比如用户虽然没给电影打分,但看了10次预告片,这个信息就被编码到了y_j里。
我在项目中用过SVD++做视频推荐。效果比纯SVD提升了8%的AUC。但代价是训练时间翻了一倍。因为每次迭代都要遍历用户的隐式行为列表。嗯,这里有个取舍:如果隐式行为太多(比如每个用户有上千条),建议先做采样,否则训练慢到怀疑人生。
3.5 工程落地要点
最后,我总结一下这些方法在工程上的注意事项:
- 离线训练 vs 在线更新:UserCF和ItemCF的相似度矩阵可以每天更新一次。SVD/SVD++的模型可以增量更新,比如用SGD(随机梯度下降)每次来一条新数据就微调一下。
- 存储选型:用户相似度用Redis的Sorted Set存,物品相似度用HBase或ES。隐向量用Faiss或Annoy做近似最近邻搜索。
- 评估指标:召回率、精确率、覆盖率。别只看AUC,覆盖率低说明推荐结果太集中,用户容易疲劳。
- 冷启动兜底:所有召回策略都要配一个“热门召回”作为保底。我习惯把热门召回的比例控制在10%-20%,既保证新用户有推荐,又不影响个性化。
我的经验:别试图用一个召回策略搞定所有场景。我通常会在线上同时跑3-5路召回(UserCF、ItemCF、SVD++、热门、内容召回),然后用一个轻量级的排序模型(比如LR)做融合。这样既保证了多样性,又提升了整体效果。
好了,这一章的内容就到这里。下一章我们会讲基于内容的召回和向量化召回,那又是另一片天地了。记住,经典方法虽然老,但它们是理解现代推荐系统的基石。把今天的内容吃透,后面学深度学习召回会轻松很多。