推荐系统基石:协同过滤原理、User-based与Item-based实现、矩阵分解技术
大家好,我是这门课的主讲工程师。今天我们来聊聊推荐系统里最经典、也最绕不开的一块基石——协同过滤。说实话,我入行那会儿,面试十家有八家都会问这个。你想想看,一个没有用户行为数据的系统,怎么做推荐?协同过滤就是最早解决这个问题的思路之一。
协同过滤的核心思想
协同过滤,说白了就是「物以类聚,人以群分」。它的假设很简单:如果两个用户过去喜欢的东西差不多,那他们未来喜欢的东西也应该差不多。反过来也一样——如果两个物品被同一群人喜欢,那它们之间就有某种相似性。
我在项目中遇到过这样一个场景:刚接手一个电商推荐系统,用户量不大,但商品种类特别多。当时我就想,用协同过滤准没错。为什么呢?因为它不需要任何物品的属性信息,只需要用户和物品的交互记录就够了。
核心公式(用户相似度计算 - 皮尔逊相关系数)
sim(u, v) = Σ(i∈I) (r_ui - r̄_u)(r_vi - r̄_v) /
√[Σ(i∈I) (r_ui - r̄_u)²] * √[Σ(i∈I) (r_vi - r̄_v)²]
其中 r_ui 表示用户 u 对物品 i 的评分,r̄_u 是用户 u 的平均评分。
User-based 协同过滤
User-based 的思路是:找到和你口味最像的邻居,然后看他们喜欢什么你没看过的。嗯,这里要注意——邻居不是地理上的邻居,而是行为上的「相似用户」。
我记得有一次做视频推荐,用户 A 喜欢看科幻片和纪录片,用户 B 也喜欢科幻片但还爱看动漫。我们算出来他俩相似度 0.78,于是把 B 看过的动漫推给了 A。结果 A 的点击率直接翻了一倍。这就是 User-based 的魅力——它能帮你发现「意料之外但情理之中」的推荐。
实现步骤其实就三步:
- 构建用户-物品评分矩阵——行是用户,列是物品,值是评分或交互行为
- 计算目标用户与其他用户的相似度——常用余弦相似度或皮尔逊系数
- 选取 Top-K 邻居,生成推荐列表——邻居喜欢的物品,按加权评分排序
避坑指南:我曾经在用户量超过 1000 万时直接用 User-based,结果相似度计算跑了整整 6 个小时。后来才意识到——User-based 的时间复杂度是 O(n²),用户量一大就扛不住。所以它更适合用户数相对少的场景,比如几百人的企业内部系统。
Item-based 协同过滤
Item-based 的思路刚好反过来:先算物品之间的相似度,然后推荐「和你喜欢的东西相似」的物品。你想想看,亚马逊的「购买此商品的用户也购买了」就是典型的 Item-based。
为什么 Item-based 在实际工业界更常用?原因很简单——物品的数量通常比用户少,而且物品之间的相似度可以离线算好存起来。我做过一个音乐推荐系统,歌曲数量 50 万,用户 2000 万。如果用 User-based,每次请求都要实时算邻居,延迟根本扛不住。但 Item-based 只需要查表,毫秒级响应。
实现步骤:
- 构建物品-用户倒排表——每个物品有哪些用户喜欢过
- 计算物品相似度矩阵——常用余弦相似度,注意要归一化
- 根据用户历史行为,召回相似物品——去重、过滤已看过的
物品相似度计算(余弦相似度)
sim(i, j) = (N(i) ∩ N(j)) / √(|N(i)| * |N(j)|)
N(i) 表示喜欢物品 i 的用户集合。这个公式也叫「归一化后的共同喜欢人数」。
User-based vs Item-based 对比
| 维度 | User-based | Item-based |
|---|---|---|
| 适用场景 | 用户数少、个性化强 | 物品数少、稳定性高 |
| 实时性 | 差(需实时算邻居) | 好(离线算相似度) |
| 推荐多样性 | 高(能发现新兴趣) | 低(容易局限在同类) |
| 冷启动问题 | 新用户无历史行为 | 新物品无交互记录 |
| 可解释性 | 「和你相似的人也喜欢」 | 「因为你喜欢这个」 |
我个人习惯是:新系统先用 Item-based 打底,等用户量上来、行为数据积累够了,再叠加 User-based 做多样性补充。说白了,没有银弹,只有合适的场景。
矩阵分解技术
协同过滤虽然好用,但有个硬伤——数据稀疏性。你想想看,一个电商平台有 1 亿商品,用户最多浏览过几百个,评分矩阵里 99.9% 都是空的。这时候算相似度,结果往往不靠谱。
矩阵分解就是来解决这个问题的。它的核心思想是:把用户和物品映射到同一个隐语义空间。说白了,就是给每个用户和每个物品学一个「隐向量」,然后用向量的内积来预测评分。
矩阵分解公式(SVD 变体)
r̂_ui = μ + b_u + b_i + p_u · q_i
其中 μ 是全局平均分,b_u 是用户偏置,b_i 是物品偏置,p_u 和 q_i 分别是用户和物品的隐向量。
我记得在做一个电影推荐项目时,直接用协同过滤的覆盖率只有 12%。换成矩阵分解后,覆盖率提升到了 38%。为什么?因为隐向量能捕捉到「潜在特征」——比如用户可能喜欢「有悬疑元素的科幻片」,这种特征在原始评分矩阵里是看不出来的。
实现时常用的优化方法是交替最小二乘法(ALS):
# 伪代码:ALS 迭代过程
for iteration in range(max_iter):
# 固定物品向量,更新用户向量
for u in users:
p_u = (Q^T * Q + λI)^(-1) * Q^T * r_u
# 固定用户向量,更新物品向量
for i in items:
q_i = (P^T * P + λI)^(-1) * P^T * r_i
注意:矩阵分解虽然强大,但有两个坑。第一,隐向量维度 k 的选择很关键——太小欠拟合,太大过拟合。我一般从 10 开始试,用验证集调参。第二,它本质上是线性模型,无法捕捉用户行为的非线性关系。后来出现的 Neural CF 就是来解决这个问题的。
实战中的选择建议
说了这么多,到底该用哪个?我给大家一个简单的决策树:
- 数据量小(用户 < 1万) → User-based,效果好且容易解释
- 物品数少且稳定 → Item-based,离线计算、线上快
- 数据稀疏、需要泛化能力 → 矩阵分解,能挖掘潜在特征
- 冷启动严重 → 先上内容推荐或热门榜,等行为数据够了再切协同过滤
我曾经犯过一个错:在只有 2000 条评分数据时强行上矩阵分解,结果隐向量学出来全是噪声。后来学乖了——数据量不够时,宁可用简单的协同过滤,也别盲目上复杂模型。
好了,这一章的内容就到这里。下一章我们会聊深度学习怎么跟协同过滤结合,也就是 Neural CF 和 Graph-based 的方法。到时候你会发现,矩阵分解其实只是起点。
课后小练习:找一份 MovieLens 数据集,分别用 User-based 和 Item-based 实现一个简单的推荐器,对比一下它们的召回率和多样性。你会发现,理论懂了和代码跑通了,完全是两回事。