第4章:协同过滤入门:User-based CF、Item-based CF、矩阵分解基础

好,咱们进入推荐系统最经典的一块——协同过滤。说实话,这玩意儿我用了快十年了,从最早的电商网站到后来的短视频平台,核心思想一直没变:物以类聚,人以群分

今天咱们聊三个方向:User-based CF、Item-based CF,还有矩阵分解。前两个是传统方法,第三个是深度学习时代的基础。我建议你把这章吃透,后面很多高级玩法都从这里长出来的。

4.1 User-based CF:找和你口味相似的人

User-based Collaborative Filtering,说白了就是「邻居推荐」。你想想看,你身边总有几个朋友,推荐电影、音乐特别对你胃口。算法做的就是这件事——找到和你行为最像的那群人,把他们喜欢的东西推荐给你。

具体怎么算?核心两步:

  1. 计算用户相似度:用余弦相似度或者皮尔逊相关系数
  2. 加权聚合:把相似用户的评分加权平均,预测你没看过的物品

举个简单的例子。假设有三个用户对两部电影的评分:

用户 《流浪地球》 《哪吒》
小明 5 3
小红 4 4
小刚 2 5

我想给小明推荐《战狼2》,但小明没看过。怎么办?先算相似度。小明和小红的评分向量是(5,3)和(4,4),余弦相似度算出来是0.98。小明和小刚是(5,3)和(2,5),相似度只有0.78。所以小红是小明的「近邻」。

如果小红给《战狼2》打了5分,那小明很可能也会喜欢。

核心公式(余弦相似度)

similarity(A, B) = (A·B) / (||A|| * ||B||)

我的经验:实际项目中,用户数往往远大于物品数。User-based CF在用户量百万级时,实时计算相似度会非常慢。我一般会离线预计算用户相似度矩阵,或者用LSH(局部敏感哈希)做近似查找。

4.2 Item-based CF:找和你喜欢的物品相似的物品

Item-based CF的思路刚好反过来——不找相似的人,找相似的物品。你给《流浪地球》打了5分,那系统就去找和《流浪地球》最像的电影推荐给你。

为什么会有这个思路?我记得2010年左右,亚马逊发了一篇论文,说Item-based CF在电商场景下效果比User-based好很多。原因很简单:物品的数量相对稳定,用户的数量却一直在涨。物品相似度矩阵可以离线算好,线上查询就是O(1)的事。

计算物品相似度时,我们看的是「哪些用户同时喜欢这两个物品」。比如:

物品 喜欢它的用户集合
《流浪地球》 {小明, 小红, 小丽}
《星际穿越》 {小明, 小红, 小刚}
《爱情片A》 {小丽, 小刚}

《流浪地球》和《星际穿越》的共同用户有小明和小红,相似度就高。而《流浪地球》和《爱情片A》只有小丽一个共同用户,相似度就低。

避坑指南:我曾经犯过一个错误——直接用所有用户计算物品相似度。结果热门物品和谁都像,推荐结果全是爆款。后来加了IUF(Inverse User Frequency)惩罚,给活跃用户降权,效果才正常。

4.3 矩阵分解:把用户和物品塞进同一个空间

前面两种方法都有个共同问题——稀疏性。用户-物品矩阵里,99%以上的格子都是空的。你想想看,一个用户看过几百部电影,但平台有几十万部,这比例太低了。

矩阵分解就是来解决这个问题的。它的核心思想:把用户和物品映射到同一个低维空间,用隐向量来表示。

比如,我们设定隐向量维度k=10。每个用户用一个10维向量表示,每个物品也用10维向量表示。用户对物品的评分,就是这两个向量的点积:

r_ui ≈ p_u · q_i

其中p_u是用户u的隐向量,q_i是物品i的隐向量。

怎么学出这些向量?最经典的方法是SVD(奇异值分解),但实际工业界更常用ALS(交替最小二乘法)或者SGD(随机梯度下降)

SGD更新公式(简化版)

e_ui = r_ui - p_u · q_i
p_u = p_u + lr * (e_ui * q_i - reg * p_u)
q_i = q_i + lr * (e_ui * p_u - reg * q_i)

其中lr是学习率,reg是正则化系数。

我个人的习惯是先用SGD快速迭代出一个baseline,再用ALS做精细调优。ALS的好处是可以并行化,适合Spark这种分布式框架。

4.4 三种方法的对比与选型

咱们用一张表总结一下:

方法 优点 缺点 适用场景
User-based CF 直观、容易解释 用户量大时计算慢、冷启动问题 用户数少、社交属性强的场景
Item-based CF 稳定、可离线预计算 无法捕捉用户兴趣变化 电商、视频等物品相对稳定的场景
矩阵分解 精度高、能处理稀疏数据 可解释性差、训练耗时 大规模推荐系统、需要高精度的场景

我的建议:如果你刚开始做推荐系统,先从Item-based CF入手。它实现简单,效果稳定,线上出问题也好排查。等业务量上来了,再逐步引入矩阵分解。别一上来就搞深度学习,容易翻车。

4.5 实战中的几个坑

最后分享几个我踩过的坑:

  • 相似度归一化:不同用户的评分习惯不一样,有人喜欢打高分,有人喜欢打低分。不做归一化,相似度计算会偏。我一般先做均值中心化,再算相似度。
  • 冷启动问题:新用户没有行为数据,协同过滤直接失效。这时候要用热门推荐或者内容特征兜底。我习惯在召回层同时跑协同过滤和内容召回,做加权融合。
  • 实时性:用户刚点了一个物品,下一秒就应该更新推荐。Item-based CF可以做到秒级更新,矩阵分解就比较吃力。我一般用增量更新,只更新受影响的那部分向量。

嗯,这一章的内容就到这。下一章咱们聊协同过滤的工程实现,包括怎么用Spark做大规模矩阵分解,怎么在线上做实时召回。到时候我会把代码贴出来,咱们一行一行过。