想象你出差到一座陌生城市,晚上想找地方吃饭。你不会去翻全网评分最高的十家店——那上面的高分未必合你口味,可能还有刷单的。你真正会做的,是给微信里最懂你口味的两三个朋友发消息:“我在 X 城,推荐哪儿吃?”他们报出的店名,往往一吃一个准。 UserCF(基于用户的协同过滤)就是把这件事自动化:先看你和所有“食客”的历史记录,找出兴趣最像的一群人;再把这群人喜欢过、而你没试过的东西,按相似度加权汇总,挑出 TopN 推给你。整个过程不需要理解物品本身,只需要知道“谁和谁口味像”。 这套思想被用在新闻 App、短视频、电商“猜你喜欢”里,是推荐系统最经典的地基。学完这篇,你会完整掌握一条推荐链路:行为画像 → 相似用户 → 近邻加权 → TopN 输出,以及冷启动和工程落地的常见打法。

岗位关联

对 AI 从业者来说,UserCF 是推荐系统的第一课——它教你“用数据说话”而非“用规则说话”。理解它之后,ItemCF、矩阵分解、向量召回都是一脉相承的进阶。

目录
  1. 可视化演示
  2. 核心思想
  3. 相似度度量
  4. K近邻选择
  5. TopN推荐
  6. 冷启动应对
  7. 工程实现
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示SGD、Momentum、Adam三种梯度下降优化器在二次损失曲面上的优化轨迹,对比收敛速度与路径,支持学习率调节与步进播放。

本节配有交互动效(进入视口后自动加载,约 3KB)。

SGD 随机梯度下降 沿负梯度方向更新参数,逐步逼近最小值 在椭圆等高线上呈Z字形振荡路径 θ = θ - η∇f(θ) SGD简单但收敛慢且易振荡

核心思想

深夜的新城市,你给两个老友发消息求推荐。他们为什么靠谱?因为他们在无数顿火锅、烧烤里摸清了你的口味——你吃辣但怕麻,爱酸汤不爱甜口。你不是“喜欢所有美食”,你有一份具体的口味档案。

UserCF 不做任何食材分析,它只依据“行为共现”:你点过、收藏过、评价过的物品,构成了你的口味档案。系统给每个用户建一份机器能读的档案,也就是一张“用户 × 物品”的兴趣矩阵,格子里的 0/1 或评分代表你是否喜欢它。

有了档案,就能回答“谁和你口味像”:两人档案重叠越多,相似度越高。找出相似用户(邻居)后,把他们喜欢而你没接触过的物品汇总排序,得到推荐。三个环节缺一不可:构建画像 → 找邻居 → 借口味生成推荐。

就像你只信那两三个懂你的朋友,UserCF 从不多管闲事地分析菜品本身,它把“懂你”这件事外包给一群人——这就是协同过滤里的“群体智慧”。

相似度度量

“像不像”不能凭感觉。你和朋友口味相似,体现在你们共同说好的馆子占比高:你们一起吃过的店都打了高分,说明口味接近;如果好评的店几乎不重叠,哪怕你们嘴上都说“好吃”,也没法互相推荐。

余弦相似度把每个用户的喜好想象成高维空间里的一个向量:每个物品是一根坐标轴,坐标值就是用户的评分。两个向量夹着一个小角,就代表两个人“朝着差不多的方向爱吃”。

另一种更朴素的尺子是 Jaccard 系数,它不看评分大小,只看重叠比例:两个人共同喜欢的东西,占他们所有喜欢过的东西的比例有多大。

还有第三把尺子:皮尔逊相关系数。它先减去每个用户自己的平均分,再做余弦计算,相当于“去中心化”。好处是能消除打分手松手紧的偏差——有人习惯全打五星,有人严格只给三星,皮尔逊看的是“相对偏好”而不是绝对分数。选哪把尺子取决于数据:0/1 行为多用余弦或 Jaccard,显式评分场景推荐皮尔逊。

sim(u,v)=∑i∈Iuvrui×rvi∑i∈Iurui2×∑i∈Ivrvi2\displaystyle \text{sim}(u,v) = \frac{\sum_{i \in I_{uv}} r_{ui} \times r_{vi}}{\sqrt{\sum_{i \in I_{u}} r_{ui}^{2}} \times \sqrt{\sum_{i \in I_{v}} r_{vi}^{2}}}
这个公式只回答一个问题:你我的朋友列表重合了多少?完全一致为 1,毫无交集为 0,非常适合点赞、收藏这类 0/1 隐式反馈数据。
  1. 交集度量共同喜好\(N(u)\) 是用户 \(u\) 交互过的物品集合,交集越大说明重合越多。
    ∣N(u)∩N(v)∣|N(u) \cap N(v)|
  2. 并集度量总范围并集表示两个人一共碰过多少种不同物品,作为归一化的基准。
    ∣N(u)∪N(v)∣|N(u) \cup N(v)|
  3. 比值反映重叠比例值越大代表交集占两者总体范围的比例越高,两人口味越像。
    Jaccard(u,v)∈[0,1]\text{Jaccard}(u,v) \in [0,1]

K近邻选择

微信好友几百人,但你挑饭搭子从来只问两三个——朋友不是越多越好,口味极度相似的那几个才有参考价值。UserCF 也一样:相似度算完之后,不是把所有人都拉进来投票,而是选出前 K 个最像的邻居。

K 是一个需要调的超参数。K 太小,比如只有 1,你的推荐完全押在一个朋友身上,他偶尔吃错一家店你就跟着踩雷;K 太大,把相似度只有 0.1 的“点头之交”也拉进来,他们的口味会稀释你的个性,推荐结果会越来越接近全站热门。经典实验中 K 通常取 50 到 200 的量级,具体数值靠离线评测(如召回率、覆盖率)来定。

选出邻居后还有两件事:筛选和加权。筛选是指只保留相似度大于 0 的用户,并把行为质量差的账号排除掉——刷单的、注册即僵尸的号,行为是噪声,不是信号。加权是指在后续评分时,用相似度本身作为权重:越像的朋友,他说的话越有分量。

K 值的选择就像组饭局:只叫一个朋友容易没得选,把整栋楼都叫上又众口难调。找到那“最懂你的几个人”,是 UserCF 效果好坏的分水岭。

TopN推荐

假设你最后问了五个朋友,大家七嘴八舌报店名。你心里会默默加权:最懂你口味的那个人推荐 A 店,你立刻记下;关系一般的朋友推荐 B 店,你半信半疑。最终选哪家,取决于“谁说的”。

候选集的构建很简单:把 K 个邻居交互过的所有物品取并集,再去掉你已经消费过的那些,剩下的就是候选池。注意,你要的是“新东西”,已经吃过的店再推荐给你毫无意义。

接下来对候选池里的每个物品打分,公式如下:

把所有候选物品的分数算完后,取 TopN 返回给用户。这就是“朋友投票”的自动版:每个人的票权不同,最懂你的人票最重,最后按得票排名端出推荐清单。

p(u,i)=∑v∈S(u,K)∩N(i)sim(u,v)×rvi∑v∈S(u,K)∩N(i)∣sim(u,v)∣\displaystyle p(u,i) = \frac{\sum_{v \in S(u,K) \cap N(i)} \text{sim}(u,v) \times r_{vi}}{\sum_{v \in S(u,K) \cap N(i)} |\text{sim}(u,v)|}
这个公式就是“加权投票”:每个邻居投一票,票的分量等于他和你的相似度;最后除以所有票权之和,防止邻居多的人分数虚高。
  1. 限定近邻中的评价者只有既是目标用户近邻、又喜欢过物品 \(i\) 的人才参与投票。
    v∈S(u,K)∩N(i)v \in S(u,K) \cap N(i)
  2. 分子做加权求和相似度 \(\text{sim}(u,v)\) 作为权重,乘以他对物品的评分 \(r_{vi}\),越像的人贡献越大。
    ∑v∈S(u,K)∩N(i)sim(u,v)×rvi\sum_{v \in S(u,K) \cap N(i)} \text{sim}(u,v) \times r_{vi}
  3. 分母是权重之和用相似度绝对值求和做归一化,避免近邻数量多导致分数天然偏高。
    ∑v∈S(u,K)∩N(i)∣sim(u,v)∣\sum_{v \in S(u,K) \cap N(i)} |\text{sim}(u,v)|
  4. 排序后截断输出所有候选物品按 \(p(u,i)\) 从高到低排序,取前 \(N\) 个作为最终推荐列表。
    R(u)={i1,i2,…,iN},p(u,i1)≥p(u,i2)≥⋯≥p(u,iN)R(u) = \{i_1, i_2, \dots, i_N\}, \quad p(u,i_1) \geq p(u,i_2) \geq \dots \geq p(u,i_N)

冷启动应对

新加的朋友没有一起吃过饭,你无法判断他口味;一家新开的店还没人去过,朋友也没法向你推荐。UserCF 的现实尴尬就在这里:没有历史行为,一切相似度都无从谈起。

新用户冷启动的常规做法是“先用热门垫底”。用户刚注册没有行为,系统先推全站热榜 Top 100,同时引导用户勾选兴趣标签、关注几个达人,快速积累第一份画像;等行为记录够厚(比如几十次点击或收藏),再无缝切换到 UserCF。这就像第一次见面先聊大众话题,熟悉之后再聊私人口味。

新物品冷启动则是 UserCF 的硬伤:一件刚上架的商品几乎没人交互过,它进不了任何人的候选集,UserCF 天生看不到它。工业界的对策是让其他模块补位——用 ItemCF(找相似物品)把新物品关联到老物品上,或者用内容特征(标题、封面、类目)做一轮召回。

冷启动的本质是“没有历史就没有相似”。所以系统要用热门榜单、内容特征、人工运营先铺一层垫子,等行为数据积累起来,再把接力棒交给 UserCF。理解了这一点,你就知道推荐系统从来不是单一模型单打独斗,而是多个策略协同作战。

工程实现

把 UserCF 从饭局变成流水线,首先就要面对规模问题:一亿用户两两计算相似度,是 \(10^{16}\) 量级的运算,任何在线请求都扛不住。工程上最经典的分层是“离线算表、在线查表”。

离线计算阶段,每天或每小时用 Spark 之类的批处理框架,把日志聚合成“用户 × 物品”矩阵,跑一遍相似度计算,输出每个用户的 Top-K 近邻表,写进 KV 存储或向量数据库。这个过程不要求实时,可以用很重的计算资源慢慢磨。

在线召回阶段,用户打开 App 时,系统直接查表取出他的 K 个邻居,再把这些邻居交互过的物品取并集、过滤已看过的,用评分公式排序,几十毫秒内返回 TopN。整个过程没有重计算,只有查表和排序,这就是“空间换时间”的典型做法。

增量更新负责保持新鲜度:新日志持续写入消息队列,周期性回流到离线任务,或者用轻量近线任务每小时刷新相似度表。用户兴趣在变,昨天的邻居可能今天就不准了。离线重算保准确,在线查表保速度,增量回流保新鲜——三者配合,才是一套健康的 UserCF 生产线。

AI 应用场景

新闻资讯:你的朋友在看什么

阶段:离线相似度计算 + 在线召回排序

解决的问题:新闻热点变化快,兴趣迁移以小时计。传统编辑推荐只能做到“大家看啥我看啥”,无法做到千人千面,也无法捕捉“这波人更关心科技、那波人更关心体育”的细分差异。

具体案例:某新闻 App 每天凌晨用 Spark 重算用户相似度表;白天用户刷新时,系统找到与他口味最像的 50 个邻居,把他们昨天点赞、转发、读完的新闻加权汇总,过滤已读,输出 10 条信息流。上线“相似的人在看”栏目后,点击率比纯热门榜提升近一倍。之所以选 UserCF,是因为新闻场景用户反馈即时、兴趣变化快,UserCF 对“跟随兴趣漂移”比静态画像更敏感。

短视频:冷启动的缓冲垫

阶段:冷启动处理 + 在线召回归类

解决的问题:新用户没有行为记录,纯 UserCF 直接失灵;但短视频平台最怕新用户前 5 分钟刷不到喜欢的内容就流失。

具体案例:新用户注册后先看到全站热度 Top 100 的短视频,同时平台引导他选择 3 个兴趣标签;积累约 20 次观看行为后,系统切换到 UserCF,找出相似老用户的观看序列做推荐。这套“热门兜底—标签初筛—UserCF 接力”的组合,让新用户次日留存率提升约 12%。UserCF 在这里不是唯一主角,却是从冷启动走向个性化最关键的一棒。

Python 演示

python57 行
import mathfrom collections import defaultdict  class UserCF:    """基于用户的协同过滤: 找相似朋友, 借他们的口味做推荐."""     def __init__(self, data, k=2):        self.data = data          # 用户 -> 交互过的物品列表 (0/1 隐式反馈)        self.k = k                # 近邻数        self.user_list = list(data.keys())     def user_items(self, user):        return set(self.data.get(user, []))     def cosine_sim(self, ua, ub):        a_items = self.user_items(ua)        b_items = self.user_items(ub)        common = a_items & b_items        if not common:            return 0.0        return len(common) / math.sqrt(len(a_items) * len(b_items))     def neighbors(self, target):        scored = []        for other in self.user_list:            if other == target:                continue            sim = self.cosine_sim(target, other)            if sim > 0:                scored.append((sim, other))        scored.sort(reverse=True)        return scored[:self.k]     def recommend(self, target, top_n=3):        target_items = self.user_items(target)        scores = defaultdict(float)        for sim, other in self.neighbors(target):            for item in self.user_items(other):                if item in target_items:                    continue                scores[item] += sim        ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True)        return [item for item, _ in ranked[:top_n]]  if __name__ == "__main__":    data = {        "小明": ["火锅", "烧烤", "奶茶"],        "小红": ["火锅", "蛋糕", "奶茶"],        "小刚": ["烧烤", "汉堡"],        "小丽": ["蛋糕", "奶茶", "汉堡"],        "小美": ["烧烤", "奶茶", "披萨"],    }    cf = UserCF(data, k=2)    for user in ["小明", "小丽"]:        print(user, "->", cf.recommend(user))
目的:用 70 行实现 UserCF 全流程——余弦相似度、K 近邻筛选、加权投票推荐,可直接运行验证。
  1. self.user_list = list(data.keys())提取所有用户名,作为待计算相似度的候选集合。
  2. common = a_items & b_items集合交集一行代码算出两人共同交互过的物品。
  3. len(common) / math.sqrt(len(a_items) * len(b_items))0/1 余弦相似度公式的落地实现。
  4. scored.sort(reverse=True)按相似度降序排列,为取前 K 个近邻做准备。
  5. scored[:self.k]只保留最像的 K 个用户,过滤掉弱相关邻居。
  6. if item in target_items: continue关键去重:绝不推荐用户已经交互过的物品。
  7. scores[item] += sim相似度作为权重累加到物品上,实现“票权不同”的加权投票。

常见误区

误区“UserCF 推荐的肯定都是大家都在看的热门东西,跟统计热门排行榜没啥区别,换个名字而已。”

正解热门排序是给所有人同一份榜单,而 UserCF 是“找到一群和你口味像的人,借他们的选择做推荐”,每个人拿到的结果都不同。它只依赖目标用户的一小群相似邻居的行为,不看全局。打个比方:热门榜是问路人“哪家店最有名”,UserCF 是问懂你的朋友“哪家店适合我”——一个是大众口碑,一个是私人定制。

为何常见:因为新用户没有行为时,系统确实先用热门榜单兜底,很多人把“冷启动期的兜底策略”误当成了 UserCF 本身。

误区“既然小红和我相似度最高,那她喜欢的所有东西都应该直接推给我,不用管什么归一化和候选筛选。”

正解相似度只描述“口味接近程度”,但相似的人也有各自独特的偏好;而且评分预测必须除以相似度之和做归一化,否则近邻多的用户分数会虚高。就像最好的朋友爱吃臭豆腐,不代表你也该被推荐臭豆腐——推荐的是“你们共同偏好之外、他验证过、你还没试过”的东西。

为何常见:直觉上“越像的人推荐越靠谱”,人们容易忽略推荐系统要解决的是“多样性”和“惊喜度”,而不只是相似度最大化。

误区“只要用户足够多,新物品被一两个人喜欢过,UserCF 就能把它推荐给相似用户,所以新物品不是问题。”

正解恰恰相反。一件新上架的物品只有极少数人交互过,它几乎进不了任何人的候选集——因为候选集是“邻居们喜欢过的物品”的并集,样本太少时它根本出不来。UserCF 对新物品是系统性短板,工业界通常引入 ItemCF(找相似物品)或内容特征来兜底。

为何常见:学习者容易把 ItemCF 的能力安到 UserCF 头上,因为两者名字都带“协同过滤”,实际一个围绕用户、一个围绕物品,冷启动特性完全不同。

误区“反正都是算相似度的公式,谁便宜用谁,选好算的那个就行。”

正解余弦相似度不处理打分的“手松手紧”差异——有人习惯全打 5 星,有人再满意也只给 3 星,余弦会把这种系统性偏差当成兴趣差异。皮尔逊先减去各自均值再算余弦,消除了评分尺度的影响。0/1 隐式反馈数据上两者差别不大,但面对 1-5 星的显式评分,皮尔逊明显更稳。

为何常见:因为初学者通常先在 0/1 数据上练习,两种公式结果差不多,就误以为换场景也差不多。

误区“为了效果稳,K 取大一点,多找几个邻居参考总比少了好,反正人多力量大。”

正解K 太大会引入兴趣很弱的“点头之交”,把他们的噪声偏好也加权进来,导致推荐结果向全站热门漂移,失去个性化;K 太小则完全押注在个别用户身上,抗噪能力差。K 的取值要结合用户规模和评测指标调,离线多跑几组对比,而不是拍脑袋取大。

为何常见:“参考的人越多越全面”是日常决策的直觉,但推荐系统里弱相关邻居不是信息增益,而是噪声。

面试题

Q1. UserCF 的核心思想是什么?

单选题 · easy

  • A.根据物品的内容特征过滤
  • B.借用相似用户的兴趣推荐← 正确答案
  • C.直接返回全站热门物品
  • D.让用户手动填写偏好

答案:B。UserCF 的关键是“人以群分”:先找兴趣相似的用户,再借他们的选择做推荐。A 是内容过滤,C 是热门榜单,D 是显式问卷,都不是协同过滤的典型思路。

Q2. UserCF 最适合下列哪类场景?

单选题 · easy

  • A.用户兴趣快速变化的新闻流← 正确答案
  • B.需要解释物品细节的问答
  • C.只读一次的静态文档库
  • D.完全无日志的离线文件夹

答案:A。UserCF 依靠用户行为找相似群体,新闻场景用户反馈即时、兴趣漂移快,正好发挥“跟着相似人群走”的优势;无日志或静态场景根本没有行为数据可挖。

Q3. 已知小明交互过 {A,B,C},小红交互过 {B,C,D},用 0/1 余弦相似度(交集 / 各自的模长乘积)计算两人的相似度约为多少?

单选题 · mid

  • A.0.333
  • B.0.500
  • C.0.667← 正确答案
  • D.0.707

答案:C。共同交互 {B,C},交集大小为 2;两人各交互 3 个物品,模长乘积为 √3 × √3 = 3,所以相似度 = 2/3 ≈ 0.667。

Q4. 皮尔逊相关系数与余弦相似度最大的区别在于?

单选题 · mid

  • A.先减去每个用户自己的评分均值← 正确答案
  • B.先对物品做全局热度归一化
  • C.直接把评分数据取对数压缩
  • D.只保留二值化的交互记录

答案:A。皮尔逊是“去中心化”的余弦,先减均值再算相似度,消除用户打分手松手紧的尺度偏差;B、C、D 都是其他预处理手段,与皮尔逊的定义无关。

Q5. 关于 K 近邻选择的说法,正确的是?

多选题 · mid

  • A.K 太小容易受噪声干扰← 正确答案
  • B.K 太大会让推荐趋于热门← 正确答案
  • C.K 越大推荐效果必定越好
  • D.K 通常需要离线评测确定← 正确答案

答案:ABD。K 小则参考样本少、抗噪差;K 大则引入弱相关邻居,个性被稀释、结果偏向热门。K 没有“越大越好”,需要像组饭局一样拿捏分寸,靠离线评测调参。

Q6. UserCF 在冷启动阶段最明显的短板是?

单选题 · easy

  • A.无法推荐刚上线的新物品← 正确答案
  • B.无法处理老用户兴趣漂移
  • C.计算复杂度相对偏高
  • D.需要物品语义特征支撑

答案:A。新物品几乎无人交互,进不了邻居的候选集,UserCF 天然“看不见”它,这是它最典型的短板;老用户漂移反而可用增量更新缓解,计算复杂度也远小于物品语义建模。

Q7. UserCF 工程实现中常用的优化手段有?

多选题 · hard

  • A.离线构建用户相似度表← 正确答案
  • B.在线只查表并召回候选← 正确答案
  • C.每次请求实时全量计算
  • D.用日志做周期性增量更新← 正确答案

答案:ABD。工业界标配是离线算表、在线查表,配合日志回流做增量更新,把重计算放到批量任务里。每次请求都全量重算相似度在亿级规模下不可行。

Q8. 在 UserCF 评分预测公式中,分母“相似度绝对值之和”的作用是?

单选题 · mid

  • A.对权重做归一化防止分数偏大← 正确答案
  • B.把评分映射到 0 到 1
  • C.抵消物品之间的热度差
  • D.让邻居数量完全失效

答案:A。分子是加权求和,邻居多的人天然分高;除以相似度绝对值之和后,得到的分数是加权平均而不是累加和,从而让不同邻居数量的用户之间可比较。

Q9. 关于 UserCF 评分预测,下列说法正确的是?

多选题 · hard

  • A.只使用近邻中喜欢过该物品的用户← 正确答案
  • B.相似度作为权重参与累加← 正确答案
  • C.需要过滤目标用户已交互的物品← 正确答案
  • D.直接用 Jaccard 值当最终得分

答案:ABC。公式限定 \(v \in S(u,K) \cap N(i)\),即只统计近邻中评价过该物品的人,并用相似度加权;推荐时还要剔除用户已消费过的物品。Jaccard 只是相似度度量,不能直接当最终预测分。

Q10. 新注册用户没有任何行为,最合理的冷启动策略是?

单选题 · extreme

  • A.热门物品兜底并快速采集偏好← 正确答案
  • B.跳过推荐直接返回空列表
  • C.随机曝光一组无关物品
  • D.强制用户注册后必须评分

答案:A。冷启动要“先铺垫子再搞个性化”:用热门榜单撑住体验,同时用兴趣标签、行为引导快速积累画像。返回空或随机曝光会流失用户,强制评分则破坏注册体验,都不是正规解法。