在社区菜市场里,老王开了一家小超市。他不靠招牌菜招揽生意,靠的是一本磨破边的“购物篮观察本”:每位顾客结账时,他都飞快记下篮子里的商品组合。日子久了,老王发现了一些奇妙规律——买咖啡的人常顺手拿奶精,买啤酒的人常搭配尿布(别问为什么),而盐和糖几乎出现在每个人的篮子里,谁都能搭。于是,当新顾客拎着一袋咖啡进门时,老王会笑眯眯地指向货架:“要不要试试旁边的奶精?” 老王的这套生意经,就是推荐系统里大名鼎鼎的“物品协同过滤”(Item-based Collaborative Filtering,简称 ItemCF)。它不做商品属性分析,不研究咖啡因和蛋白质,只靠“一起被买走”这个行为信号,就能算出物品之间的默契程度。这套思想朴素却极有力量,是电商、视频、新闻等 App 里“猜你喜欢”的老祖宗级算法。 这篇教程,我们就跟着老王一起,从“购物篮观察本”出发,一步步搭建共现矩阵、计算物品相似度、惩罚热门商品、生成 Top-N 推荐,最后对比它和“以人找人”的 UserCF 各自的门道。读完你就能明白:为什么“买过这个的人还买了那个”这句话,藏着推荐系统的半壁江山。
理解 ItemCF 是踏入推荐系统领域的必修课,面试必考、上线常用。掌握了它,你就握住了“行为数据炼金术”的第一把铲子——这是无数推荐算法工程师职业生涯的起点。
可视化演示
展示共现矩阵构建与相似度计算、ItemCF与UserCF对比、及基于物品相似度的推荐生成流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
共现矩阵构建
老王收银时养成了一个习惯:每来一位顾客,他就在小本子上画一个小格子,记下购物篮里有哪几样。晚上关门,他对着格子发愣,渐渐看出门道——咖啡和奶精总是成对出现,啤酒和尿布也常一起露面。他把这些“成对出现”的次数填进一张货架×货架的表格里:横轴一种货,纵轴一种货,交叉的格子就是“这对商品同时被买走的次数”。这张表格,就是推荐系统里的“共现矩阵”。
在真实的推荐系统里,老王的“小本子”就是用户行为日志——点击、收藏、加购、下单,每一条行为都是一个虚拟“购物篮”。所谓构建共现矩阵,就是把日志翻译成一张物品×物品的计数表:co[i][j] 表示“同一用户对物品 i 和物品 j 都产生过行为”的人数。构建过程分三步:第一步,把日志按用户聚合,得到每人一个行为物品集合;第二步,对每位用户集合里的物品两两配对,在矩阵对应格子加 1(顺便也给自己加 1,于是对角线记录了每个物品自身的流行度);第三步,汇总所有用户,得到完整的 M×M 矩阵。M 是物品总数,真实系统里可能有千万级,矩阵必然稀疏,通常用字典或稀疏矩阵存储。
老王后来把本子换成了大号表格纸,纸还是那张纸,只是格子越填越多。他心里明白:这本“观察本”是一切推荐的起点——没有它,后面所有的默契度都是空中楼阁。
- 符号约定设 U 是全部用户集合,I_u 是用户 u 购买过的物品集合,我们要遍历每一位顾客。
- 示性函数花括号内的条件成立时为 1,不成立时为 0,像一盏只有“亮/灭”两种状态的灯。
- 同时包含判断要求“顾客 u 的篮子里同时有物品 i 和物品 j”,两件事缺一不可。
- 全用户求和把每位顾客的 0/1 结果累加,最终得到 i 与 j 的共现总次数。
相似度计算
表格填满之后,老王发现一个烦恼:盐和糖几乎是“百搭王”,和谁都共现几十次;而小众的枫糖浆只和松饼共现了两次。如果直接按次数排序,那人人都买的盐会霸占所有推荐位。老王灵机一动:不能看绝对次数,要看“比例”——枫糖浆虽然只出现两次,但买它的人几乎都会拿松饼,这才是真正的默契。
这个“比例思维”在算法里就是余弦相似度。物品 i 和 j 的相似度 = 同时买过两者的用户数,除以各自购买人数的几何平均。直觉上,它衡量的是“两个物品在同一个人身上发生的重合程度”,与各自热度解耦。分母 sqrt(|N(i)|×|N(j)|) 的作用很关键:物品越热门,分母越大,即使共现次数很高,相似度也会被拉回合理水平。结果是:盐和糖虽然共现次数巨大,但相似度平平;咖啡和奶精的相似度反而登顶。该矩阵天然满足对称性 sim(i,j) = sim(j,i),因为“共同购买者”这个概念不分方向。
老王在观察本旁边贴了一张“默契度排行表”。从此他不再数次数,只看排行——这排行,就是我们要离线算好、线上随时能查的物品相似度表。
- N(i) 的含义表示购买过物品 i 的用户集合,即“谁是 i 的回头客”。
- 交集计数既买过 i 又买过 j 的用户数,也就是共现矩阵里的原始计数。
- 物品热度积两种物品各自购买人数的乘积,热度越高的物品这个值越大。
- 开根号归一化将乘积压缩为几何平均量级,分母与热度解耦后,相似度大致落在 0 到 1。
热门物品惩罚
还有一个更隐蔽的问题。隔壁小区有个“扫货达人”李姐,每周都推着三辆购物车扫荡超市,一次买五十样东西。她只要在店里,盐、糖、酱油、纸巾统统进篮子,于是她一个人就给“盐-糖”贡献了 1 次共现,其说服力和一位只买“咖啡+奶精”的老顾客完全一样。老王觉得不公平:买得越多的人,提供的信息越廉价。
算法里对应的改进就是“活跃用户惩罚”:一个人买过的物品越多,他在共现贡献上的话语权就越小。实现方式是在累加共现时,不再加 1,而是加 1/log(1+|N(u)|),其中 |N(u)| 是这位用户买过的物品总数。对数函数把惩罚控制得很温和:买 3 样物品的用户贡献约 0.62,买 30 样的用户贡献约 0.30,买 300 样的用户贡献约 0.18——不至于把大户一票否决,但足以让“理性小篮子”的声音更清晰。加上这个惩罚后,共现矩阵的每一格从“计数”变成“加权计数”,相似度计算结果对热闹用户不再过敏。
老王给观察本立了条新规矩:凡是推着三辆购物车的顾客,他们的篮子最多只算“半票”。规则一改,枫糖浆和松饼的默契排名嗖嗖往上涨,盐和糖彻底退出了推荐舞台。
- 遍历共同购买者逐个考察同时买过 i 和 j 的每一位用户,他们才是这对物品关系的“见证人”。
- 活跃度惩罚用户买过的物品 |N(u)| 越大,该项越小,贡献被自动压低。
- 对数平滑用对数把用户规模的差异压缩到温和范围,避免惩罚过猛、误伤正常用户。
- 保持归一化分母与基础版相同,继续用热度几何平均抑制热门物品,双重保险。
推荐生成流程
一个周末,老顾客小丽拎着咖啡和牛奶来结账。老王扫了一眼篮子,心里立刻有了数:咖啡最默契的是奶精,牛奶最默契的是面包。他乐呵呵地说:“要不要来盒奶精?松饼今天也进货了。”小丽惊讶地问怎么知道,老王神秘一笑——他的推荐流程其实就三步:先把顾客篮子里的每件商品都当成跳板,翻“默契度排行表”找各自最匹配的候选;再给每个候选打个总分;最后按总分排个队,取前两名,并且绝不推荐已经买过的东西。
算法化之后,这个过程就是候选生成 + 打分 + 截断。给定用户 u 的历史物品集合 I_u,对每个候选物品 j,分数等于用户历史物品与 j 的相似度之和。几个工程细节:一是 j 不能是用户已经买过的,否则推荐了等于没推荐;二是在线服务时不需要现场算相似度,只需查离线预计算好的相似度表,所以响应能在毫秒级;三是 Top-N 的 N 通常取 5~20,配合重排策略决定最终展示。整个流程就是“离线算关系、在线查表打分”。
老王后来把“默契度排行表”换成了一沓厚卡片,每张卡片写着一种商品和它的 Top 名单。接客时他就翻卡片、算总分、报菜名——这就是 ItemCF 推荐服务在真实系统里每天反复上演的剧本。
- 历史集合用户 u 已购买或喜欢过的物品集合,是推荐的“出发港口”。
- 遍历历史物品逐个取出用户买过的物品作为“跳板”,它们各自的相似关系都要用上。
- 相似度累加把候选物品 j 与每个历史物品的相似度相加,总分越高越靠前。
- 排除已购候选物品必须是用户还没买过的新东西,否则推荐毫无意义。
双CF对比分析
超市对面开了一家小饭馆,老板娘张姐人称“活人脉”。她记性极好,记得谁和谁口味相近:小丽爱喝咖啡,那八成也爱喝张姐调的热可可。张姐的推荐逻辑是“人以群分”,而老王是“物以类聚”。两种生意都红火,但适用场景完全不同。
算法世界里的这两派,正是 ItemCF 和 UserCF(用户协同过滤)。UserCF 先找与目标用户口味最相似的“邻居”,再推荐邻居喜欢而用户没看过的东西;ItemCF 则绕开人,直接在物品之间建立关系。它们的计算成本结构差异巨大:两者的主要开销都在“两两配对”。ItemCF 是物品×物品,共 M² 对,每对要扫行为数据,记为 O(M²P);UserCF 是用户×用户,共 U² 对,记为 O(U²P)。当物品数远小于用户数(电商:几百万商品 vs 几亿用户),ItemCF 明显更划算;反过来在新闻场景,文章每日海量新增而相对用户数少时,UserCF 更合适。推荐效果上,ItemCF 解释性强、推荐偏“相关”;UserCF 更容易发现新兴趣、推荐偏“惊奇”。时效性上,ItemCF 的相似度表可离线静默更新,UserCF 对用户新行为更敏感,也更容易被实时行为牵着走。
老王和张姐互相不服气,但最后都明白:店铺位置决定了打法。超市里货架关系稳定,老王的本子越用越灵;饭馆里熟客关系密切,张姐的人脉越攒越广。选 UserCF 还是 ItemCF,先数数自己是“货多”还是“人多”。
- 符号定义M 为物品总数,U 为用户总数,P 为平均每位用户的行为条数。
- ItemCF 的成本主要开销是物品两两配对,与物品数的平方成正比。
- UserCF 的成本主要开销是用户两两配对,与用户数的平方成正比。
- 选择依据当物品数远小于用户数时,M² 远小于 U²,ItemCF 的性价比优势巨大。
实际应用经验
老王的观察本出了名,连锁超市来取经。老王总结三条生意经:第一,货架关系要趁夜里人少时算好(离线计算);第二,白天营业时直接翻卡片,不能现场算(在线查询);第三,新上架的货没有历史,得先用试吃等活动攒人气,等有顾客买了才能进排行榜(冷启动托底)。
这三条生意经,恰好对应 ItemCF 在工业界的标准落地方式。电商的“买了又买/看了又看”、内容平台的“相关阅读”、视频网站的“看过还看”,底层往往都有 ItemCF 的身影——因为它相似度可离线算、线上延迟极低、解释成本几乎为零。工程上常用“分桶 + 增量更新”:后台任务每半小时用最近行为增量更新共现计数,再定期全量重算相似度表;推荐时按用户实时行为从表中取关联物品做召回。配合热度惩罚和类目去重,能有效避免“满屏都是爆款”和“连续推同类”两个经典翻车现场。对新品,通常先用内容相似或热度推荐过渡,积累足够行为后再进入 ItemCF 候选池。
老王最后在观察本扉页写了句话:“货架会说话,但只有听的人多了,它才说得响。”ItemCF 的价值就在于此——它把千万次沉默的购买行为,翻译成一句句成功的推荐词。
AI 应用场景
电商平台“买了又买”模块
阶段:召回阶段
解决的问题:传统按商品类目推荐不精准——用户刚买完手机,系统还在推手机。ItemCF 根据全站行为共现,能发现“手机 + 碎屏险 + 手机壳”这类跨类目但强关联的组合,就像老王发现“啤酒 + 尿布”一样。
具体案例:某 3C 电商平台在用户加购相机后,系统 50ms 内从离线相似度表中召回“相机包、备用电池、SD 卡”,推荐点击率比类目推荐提升 38%。用户看到“买过相机的人还买了这些”,信任感和转化率双升。
内容平台“相关阅读”
阶段:召回与去重
解决的问题:编辑人工打标签维护量大、时效差,且文章风格千差万别难以归类。ItemCF 用“读这篇文章的人也读了那篇”自动生成关联关系,无需任何 NLP 模型。
具体案例:某新闻 App 在文章详情页底部接入 ItemCF 相似文档召回,从 200 万篇文章中快速生成关联列表,详情页阅读转化提升 22%。运营同学只需要在候选里人工把关,人力成本压缩 60%。
视频平台“看完了还能看什么”
阶段:召回阶段辅助排序
解决的问题:长尾视频缺乏内容标签,UserCF 的计算量随用户数暴涨。ItemCF 提前算好“影片-影片”关联图谱,电视端、手机端随取随用。
具体案例:某 OTT 平台用 ItemCF 构建影片关联图谱,在电视端详情页做“双片捆绑”连播推荐,用户人均观看时长提升 15%,且夜间高峰期的接口耗时始终稳定在 80ms 以内。
Python 演示
# -*- coding: utf-8 -*-"""ItemCF 物品协同过滤:基于购物篮共现的推荐演示"""import mathfrom collections import defaultdict def build_dataset(): """模拟用户-物品行为数据:每个集合代表一位用户的购物篮""" return { '小明': {'咖啡', '牛奶', '面包'}, '小红': {'咖啡', '啤酒', '尿布'}, '小王': {'牛奶', '面包', '尿布'}, '小丽': {'咖啡', '牛奶', '橙汁'}, '小刚': {'啤酒', '尿布', '面包'}, '小美': {'咖啡', '橙汁', '面包'}, } def item_similarity(dataset): """计算物品相似度矩阵,含活跃用户惩罚""" co_count = defaultdict(float) # 物品对共现加权次数 item_freq = defaultdict(int) # 每个物品被多少人买过 for user, items in dataset.items(): penalty = 1.0 / math.log1p(len(items)) # 活跃用户惩罚因子 for item in items: item_freq[item] += 1 for i in items: for j in items: if i != j: co_count[(i, j)] += penalty sim = {} for (i, j), cnt in co_count.items(): sim[(i, j)] = cnt / math.sqrt(item_freq[i] * item_freq[j]) return sim def precompute(sim): """把相似度对整理成 物品 -> 相似物品列表,便于查询""" item_sim = defaultdict(list) for (i, j), s in sim.items(): item_sim[i].append((j, s)) for i in item_sim: item_sim[i].sort(key=lambda x: x[1], reverse=True) return item_sim def recommend(user, dataset, item_sim, top_n=3): """给用户推荐未买过的物品,按相似度加权总分排序""" history = dataset[user] scores = defaultdict(float) for item in history: for j, s in item_sim[item]: if j not in history: scores[j] += s return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_n] if __name__ == '__main__': data = build_dataset() sim = item_similarity(data) table = precompute(sim) print('咖啡最相似的物品:', [x[0] for x in table['咖啡'][:2]]) print('小明历史篮子:', sorted(data['小明'])) print('为小明推荐:', [x[0] for x in recommend('小明', data, table)])
penalty = 1.0 / math.log1p(len(items))活跃用户对数惩罚:篮子越满,这票越便宜co_count[(i, j)] += penalty共现加权计数:矩阵的每一格在累加sim[(i, j)] = cnt / math.sqrt(item_freq[i] * item_freq[j])余弦归一化:用热度几何平均压住爆款item_sim[i].sort(key=lambda x: x[1], reverse=True)把相似物品按默契度降序排好if j not in history:过滤已购物品,保证推荐的新鲜感return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_n]加权求和后取 Top-N
常见误区
误区ItemCF 不就是把同类商品(比如都是数码产品)推荐给用户吗?我认为它靠的是商品分类和标签,系统得先读懂商品是什么。
正解ItemCF 完全靠用户行为共现,不看商品属性。咖啡和奶精、啤酒和尿布之所以被绑在一起,是因为大量用户把它们同时放进篮子,而不是因为它们长得像。依赖内容属性的路线叫“基于内容的推荐”(CB),是另一条独立的技术脉络,两者的数据原料和算法逻辑都不相同。
为何常见:“物品相似”这个说法容易让人联想到商品本身的相似,初学者很难想到相似性可以从购买行为里“统计”出来。
误区“牛奶和面包”被同时买走 100 次,“咖啡和奶精”只有 50 次,所以牛奶和面包更相似。我认为直接比共现次数就行。
正解直接比次数会被热门物品带偏。盐、糖这种人人都买的物品,和任何东西都有高共现。必须看“比例”:分母除以 sqrt(N(i)×N(j)) 后,高频物品的相似度被压低,“咖啡-奶精”这种强绑定关系才浮出水面。老王的经验是:不要数盐几次入篮,要看松饼的顾客里有几个人同时拿了枫糖浆。
为何常见:人脑直觉容易用绝对数量判断关系强弱,但推荐场景里所有信号都是相对量,不归一化就会被头部流量掩盖。
误区ItemCF 只要算共现就能推荐,那新品上架后很快就能被推起来。我认为 ItemCF 天然能解决冷启动。
正解恰好相反,新品没有任何用户行为数据,共现矩阵里它是一行全零,谁都不认识它。ItemCF 对“新用户”友好——用户只要有一两个行为就能获得推荐;但“新物品”是它的盲区。新品通常要先用内容相似或热度策略顶一阵,积累行为后才能进入 ItemCF 的候选池。
为何常见:把“基于行为”误当成“无需数据”,忽略了行为本身就是数据,而新品恰恰最缺行为。
误区ItemCF 能离线算好相似度、线上毫秒响应,那它一定比 UserCF 好,没必要学 UserCF。
正解两者适用场景不同:物品数远小于用户数(电商、视频)时 ItemCF 更划算;用户数远小于物品数、或需要挖掘小众新兴趣(新闻、社交)时 UserCF 更擅长发现“惊奇推荐”。它们是互补的,实践中常同时上、按策略混合,成熟系统里往往“两条腿走路”。
为何常见:ItemCF 在工业界曝光率高,学习者容易以偏概全;复杂度公式 O(M²P) 与 O(U²P) 才是理性的判断依据。
面试题
Q1. ItemCF 的核心思想是?
- A.通过用户属性相似推荐
- B.依靠商品内容标签计算
- C.基于行为共现推荐相似物← 正确答案
- D.依据热度排序推荐热门
答案:C。ItemCF 从“一起被买走”的行为记录里提炼物品关系,不看属性标签、不依赖内容特征,因此 C 正确。A 是人口统计思路,B 是基于内容的推荐,D 是热门榜策略,都与 ItemCF 的核心机制无关。
Q2. 共现矩阵元素 co[i][j] 的含义是?
- A.物品 i 被用户买过的总次数
- B.物品 j 被用户买过的总次数
- C.同时买过物品 i 和 j 的用户数← 正确答案
- D.所有用户购买行为的平均次数
答案:C。共现矩阵第 i 行第 j 列的值,是“同时对物品 i 和 j 产生行为的用户数”。A、B 只描述单个物品的频率,对应的是对角线;D 则完全偏离定义。可以把它想成老王的观察本里“某一对商品同时被放进篮子的总次数”。
Q3. 关于相似度公式分母 sqrt(|N(i)|×|N(j)|) 的作用,正确的是?
- A.放大热门物品的相似影响
- B.抑制热门物品的干扰程度← 正确答案
- C.让所有物品的权重完全相同
- D.把相似度归一成概率数值
答案:B。分母用两种物品各自热度做几何平均归一,热门物品分母大、相似度被压低,故选 B。它不会放大热门(A 反了),也不是一视同仁(C 忽略了热度差异),更不产生概率语义(D 无此性质)。
Q4. ItemCF 公式中 1/log(1+|N(u)|) 的惩罚对象是?
- A.历史上购买行为极少的用户
- B.历史上行为过于丰富的用户← 正确答案
- C.长期无人问津的冷门物品
- D.与所有物品都不相似的物品
答案:B。|N(u)| 是用户买过的物品总数,买得越多、log 越大、惩罚因子越小,针对的是“购物车太满”的活跃用户。行为极少的用户因子接近 1,权重反而高;C、D 与公式符号毫无关系。
Q5. 给定用户历史物品集合 I_u,候选物品 j 的 ItemCF 得分计算方式是?
- A.直接取共现矩阵的元素值
- B.对历史物品的相似度加权累计← 正确答案
- C.取相似物品的平均热度值
- D.对每个候选物品统一打满分
答案:B。ItemCF 的打分是把候选物品 j 与用户所有历史物品的相似度求和,即 B。A 少了“遍历历史并累加”的过程;C 是分母的变形,属于归一化部件;D 等于放弃排序,毫无信息量。
Q6. 物品数 M、用户数 U,ItemCF 离线训练的主要复杂度量级是?
- A.与用户总数的平方成正比
- B.与物品总数的平方成正比← 正确答案
- C.与用户和物品的乘积成正比
- D.与用户平均行为数完全无关
答案:B。ItemCF 的主体是物品两两配对(共 M² 对),每对需要扫描行为数据 P 条,量级是 O(M²P),故选 B。A 是 UserCF 的量级,C、D 都严重低估了训练成本。
Q7. 新产品上架后,ItemCF 推荐效果差的最主要原因是?
- A.相似度公式存在计算错误
- B.其对应共现行向量全为零← 正确答案
- C.新品本身质量不够优秀
- D.归一化参数设置得不当
答案:B。新品没有用户行为,它在共现矩阵中对应的一行/一列全是零,相似度无从算起,B 是主因。公式本身无误(A 排除)、与用户口味无关(C 排除)、也不是参数问题(D 排除),需要内容或热度策略先托底。
Q8. 下列哪些属于 ItemCF 的合理改进手段?
- A.对活跃用户施加惩罚降权← 正确答案
- B.对相似度矩阵做归一化处理← 正确答案
- C.离线预计算物品相似度表← 正确答案
- D.对候选物品做随机加权打分
答案:ABC。活跃用户惩罚(A)、相似度归一化(B)、离线预计算(C)都是工业界常规改进。随机打分(D)会彻底破坏排序稳定性,不可能用于任何推荐系统。
Q9. 相比 UserCF,ItemCF 的典型优势有?
- A.物品少用户多时算力更友好← 正确答案
- B.能解释看了这个还看那个← 正确答案
- C.相似度表可离线预计算好← 正确答案
- D.完全无需用户任何行为数据
答案:ABC。ItemCF 开销随物品数增长,物品少用户多时更省算力(A);它推荐“和已买物品相似”的商品,解释自然(B);相似度表离线生成、线上查表(C)。选项 D 否定行为数据是荒谬的——没有行为就没有共现,ItemCF 寸步难行。
Q10. 关于 ItemCF 与 UserCF 的说法,正确的有?
- A.新闻场景 UserCF 更能追踪热点← 正确答案
- B.电商场景 ItemCF 性价比更高← 正确答案
- C.ItemCF 相似度需实时在线算
- D.UserCF 训练成本与用户数平方相关← 正确答案
答案:ABD。新闻文章海量且热点变化快,用户相似关系能更快追热点(A 正确);电商物品稳定,ItemCF 性价比高(B 正确);ItemCF 相似度是离线算好、线上查表,无需实时计算(C 错误);UserCF 的离线复杂度确实是 O(U²P),与用户数平方相关(D 正确)。口诀:人少货多用 ItemCF,人多货少用 UserCF。