2. 协同过滤基础:基于用户的协同过滤、基于物品的协同过滤、相似度计算方法

好,咱们今天聊聊协同过滤。说实话,这是推荐系统里最经典、最接地气的一类算法。我入行那会儿,第一个上线的推荐模块用的就是协同过滤。你想想看,它的核心思想其实特别朴素——物以类聚,人以群分

说白了,就是利用用户和物品之间的交互行为(比如点击、购买、评分),来发现相似的用户或相似的物品,然后做推荐。嗯,这里要注意,它不需要知道物品的内容是什么,也不需要知道用户的画像,全靠行为数据说话。

2.1 基于用户的协同过滤(UserCF)

这个思路很直接:找到和你口味相似的人,把他们喜欢的东西推荐给你

我在项目中遇到过这样一个场景:一个新用户注册后,只看了两本技术书。系统通过UserCF发现,有几个老用户也看过这两本书,而且他们还买了《深度学习入门》和《算法导论》。好,那这两本书就可以推给新用户了。

具体步骤分三步:

  1. 计算用户相似度:根据用户的历史行为,算出用户之间的相似度矩阵。
  2. 找到最近邻:对目标用户,找出相似度最高的K个用户(邻居)。
  3. 生成推荐:把邻居们喜欢、但目标用户还没碰过的物品,按得分排序推荐。

这里有个关键点——用户相似度怎么算? 我习惯用余弦相似度或者皮尔逊相关系数。咱们后面会细讲。

UserCF的优缺点

  • 优点:推荐结果有社交解释性(“和你相似的人也买了...”),用户容易接受。
  • 缺点:用户数量大时,相似度矩阵计算量爆炸。而且新用户冷启动问题严重——没有行为数据,就找不到邻居。

我的经验:UserCF适合用户数相对稳定、物品更新快的场景,比如新闻推荐。我曾经在一个社交平台上用过,效果不错,但每天凌晨算相似度矩阵时,服务器CPU直接拉满...后来我加了增量更新才缓解。

2.2 基于物品的协同过滤(ItemCF)

这个更常用,也是电商平台的主流做法。核心逻辑是:推荐和你喜欢过的物品相似的物品

举个例子:你买了一本《Python编程》,系统发现买这本书的人大多也买了《数据科学入门》。那系统就会把后者推荐给你。注意,这里不是看物品内容是否相似,而是看行为模式上的共现关系

步骤也很清晰:

  1. 计算物品相似度:根据用户的行为记录,计算物品之间的相似度矩阵。
  2. 生成推荐:对目标用户喜欢的物品,找出最相似的N个物品,去重后推荐。

你可能会问:为什么ItemCF比UserCF更流行?

原因很简单:物品数量通常比用户数量少,而且物品的相似度相对稳定。你想想看,用户的口味可能天天变,但《三体》和《流浪地球》的相似关系,一年半载都不会变。所以物品相似度矩阵可以离线算好,线上直接查,性能好很多。

避坑指南:我曾经犯过一个错——直接用所有用户的行为算物品相似度,结果热门物品被过度推荐。后来我加了惩罚热门物品的机制(比如用log函数降权),推荐多样性才提上来。

2.3 相似度计算方法

相似度计算是协同过滤的基石。选错了方法,推荐效果直接崩。我整理了几种最常用的:

方法 公式 适用场景 我的建议
余弦相似度 cos(A,B) = (A·B) / (||A|| ||B||) 评分数据、隐式反馈(点击/购买) 最常用,简单稳定
皮尔逊相关系数 r = Σ((x-μx)(y-μy)) / (n·σx·σy) 评分数据(用户评分尺度不同时) 能消除评分偏差,我偏好这个
Jaccard相似度 J(A,B) = |A∩B| / |A∪B| 只有购买/收藏等布尔行为 简单粗暴,适合冷启动
调整余弦相似度 减去用户平均评分后再算余弦 评分数据,用户评分习惯差异大 比普通余弦更准,但计算量大

咱们重点说说余弦相似度皮尔逊相关系数的区别。

余弦相似度看的是两个向量的方向是否一致。比如用户A和B都买了5本书,但A给每本打5分,B给每本打3分。余弦相似度会认为他们很相似(方向一致),但皮尔逊相关系数会扣掉各自的平均分,发现其实评分模式不同。

我个人习惯:如果数据是评分,优先用皮尔逊;如果是点击/购买这种0/1数据,用余弦或Jaccard就够了。

代码示例:Python实现余弦相似度

import numpy as np

def cosine_similarity(vec_a, vec_b):
    dot_product = np.dot(vec_a, vec_b)
    norm_a = np.linalg.norm(vec_a)
    norm_b = np.linalg.norm(vec_b)
    if norm_a == 0 or norm_b == 0:
        return 0.0
    return dot_product / (norm_a * norm_b)

# 示例:两个用户的评分向量
user1 = [5, 3, 0, 1]
user2 = [4, 0, 0, 1]
print(cosine_similarity(user1, user2))  # 输出:0.975

小技巧:实际工程中,我一般会把相似度矩阵存成稀疏矩阵格式(比如用scipy.sparse),不然内存分分钟爆掉。尤其是UserCF,用户数一多,矩阵就是几百万乘几百万,普通机器根本扛不住。

2.4 UserCF vs ItemCF:怎么选?

这个问题我经常被问到。直接给结论:

  • 用户数少、物品数多(比如新闻网站):用UserCF。因为用户少,相似度矩阵小,而且新闻更新快,物品相似度不稳定。
  • 用户数多、物品数少(比如电商、视频网站):用ItemCF。物品相似度稳定,可以离线算好,线上响应快。
  • 冷启动场景:两个都不太灵。这时候我一般先用热门推荐或者内容推荐顶一阵,等行为数据积累够了再切协同过滤。

嗯,这里要提醒一句:协同过滤不是万能的。它依赖用户行为数据,如果数据稀疏(比如大部分用户只交互过1-2个物品),相似度算出来就不准。我踩过这个坑——当时一个App刚上线,用户行为很少,UserCF推荐出来的东西跟随机推荐差不多。后来我加了矩阵分解(咱们后面章节会讲),才把效果拉起来。

好了,这一章就到这里。下一章咱们聊聊矩阵分解,那是协同过滤的进阶版,也是很多工业级推荐系统的基石。