1. 先搞清楚两件事:一个算“一起买”,一个算“像你的人”
1.1 关联规则是找“购物篮里的规律”
关联规则要做的事情,说人话就是:从一堆历史订单里找出“哪些东西经常被同时买”。
这个思路最早来自线下零售的购物篮分析。顾客结账时收银台扫出来的那一串商品,就是一个“购物篮”。当超市的啤酒和尿布经常出现在同一个篮子里,就会产生一条规则:“买了啤酒的人,大概率也会买尿布”。这就是关联规则的核心表达方式,用一句很简单的逻辑来描述它:
买了 A 的人,有多大概率会买 B?
这条逻辑用形式化表达就是A -> B,意思是“从 A 可以推出 B”。注意,这里的“推出”不是因果关系的推出,而是“同时出现”的统计关联。啤酒和尿布之间没有任何因果关系,但它们在数据上频繁共现,就足以支撑一条运营策略:把尿布促销信息和啤酒放到同一个推荐位。
不少新手容易在这里犯迷糊,以为关联规则学的是“用户的偏好”。其实它学的不是人,而是商品之间的关系。整条规则里根本没有用户维度,只有商品组合和组合出现的统计规律。这也是它和协同过滤最根本的分水岭:关联规则不管你是谁、不管你的历史偏好,它只看“哪些商品天生就喜欢凑在一起”。
1.2 协同过滤是找“人跟人、物品跟物品的相似”
协同过滤的名字翻译得不太好,初次接触的人很容易被“协同”两个字带偏,以为它是什么多人协作的算法。它其实指的是:借用群体的行为来共同完成过滤,也就是“大家一起帮你筛东西”。
它有两个经典分支:
- 基于用户的协同过滤(UserCF):找出和你行为习惯相似的一群人,把这些人喜欢而你没见过的物品推荐给你。
- 基于物品的协同过滤(ItemCF):找出和你已经买过、看过、点过赞的物品相似的其它物品,然后推给你。
注意,协同过滤里的“相似”,是基于行为数据的相似,不是基于商品属性的相似。两个商品被同一批人频繁一起买、一起看,它们就算“行为相似”;两个用户频繁对同样的物品产生正向反馈,他们就算“行为相似”。
所以协同过滤学的是“人或物品的邻居关系”。它天然依赖“用户-物品”的行为矩阵,行列分别是人和物品,格子里的值表示行为强度,比如是否点击、是否购买、打了多少分。
你可以这么理解:关联规则关心的是“货架上的组合规律”,协同过滤关心的是“人群中的品味圈层”。同样是推荐一瓶啤酒,关联规则推它是因为啤酒和三文鱼经常出现在同一张订单里,协同过滤推它是因为你所在的用户群普遍喜欢精酿,而你还不知道这个牌子。
1.3 一句话版本
关联规则回答的问题是“买了它之后,接下来还可能买什么?”;协同过滤回答的问题是“和我相似的人 / 和我喜欢的东西相似的东西,到底是什么?”
这两种算法的输出形式也不同。关联规则输出的是“规则”,比如“面包 -> 黄油,置信度 0.42”;协同过滤输出的是一串带分数的物品排序,比如给用户推荐列表[item7, item3, item9],每个物品带一个预测得分。
这个区别决定了很多事:关联规则的结果好解释,运营可以直接看懂;协同过滤的结果是个性化的,每个人看到的都不一样,但解释起来要费一番功夫。两者没有绝对的优劣,只是解决的是不同层面的问题。
2. 各自的核心指标到底在算什么
2.1 关联规则的三件套:支持度、置信度、提升度
关联规则靠三个指标筛选和排序规则,分别是支持度、置信度、提升度。这三个指标不是拍脑袋定的,它们分别从“普遍性”“可靠性”“有效性”三个角度考核一条规则。
先定义基础符号。假设我们有 N 条交易记录,support(A)表示包含商品 A 的交易数量占全部交易数量的比例,support(A∪B)表示同时包含 A 和 B 的交易比例。
支持度(Support):
support(A -> B) = support(A ∪ B) = A和B同时出现的订单数 / 总订单数支持度的含义是“这条规则覆盖了多少人”。如果啤酒和尿布只在 100 张订单里出现过 1 次,那么支持度是 0.01,说明这条规则虽然存在,但能影响的人群太少了。实际项目中,支持度太低意味着样本不足,规则可能是噪声,一般会设一个最小支持度阈值把它过滤掉。
置信度(Confidence):
confidence(A -> B) = support(A ∪ B) / support(A)置信度的含义是“买了 A 的人里面,有多少人还买了 B”。假如买了啤酒的人里有 60% 也买了尿布,那么啤酒 -> 尿布的置信度就是 0.6。置信度越高,说明 A 对 B 的预测能力越强,规则越可靠。
但置信度有个致命缺陷:它不考虑 B 本身的热门程度。如果尿布本身就是一个高销量商品,不管买不买啤酒,所有订单里都有 50% 的订单包含尿布,那么啤酒 -> 尿布置信度 0.6 看起来很高,实际比基准概率 0.5 只高了一点,这个提升非常有限。所以单看置信度会误判很多规则。
提升度(Lift):
lift(A -> B) = confidence(A -> B) / support(B)提升度衡量的是“买了 A 对买 B 的概率提升程度”。分子是“买了 A 的人中买 B 的概率”,分母是“所有人中买 B 的概率”,两者的比值就是提升度。
lift > 1:A 和 B 正相关,买 A 会显著提升买 B 的概率,这是我们要的规则。lift = 1:A 和 B 互相独立,买不买 A 都不影响买 B,规则没价值。lift < 1:A 和 B 负相关,买 A 反而会降低买 B 的概率。
实际项目中,我筛选规则的习惯是先设一个最小支持度,保证规则有足够的样本量,再按置信度或提升度排序。如果目标是做捆绑促销、寻找强关联组合,重点看提升度;如果目标是做“买了A再推荐B”的交叉销售,重点看置信度。
2.2 协同过滤的相似度:余弦、皮尔逊、Jaccard
协同过滤的核心计算是相似度。你要给用户 A 找邻居,就要算 A 和其他用户的相似度;要给电影 X 找相似品,就要算 X 和其他电影的相似度。不同的数据形态,用的相似度算法也不同。
最常用的是余弦相似度。把用户的评分向量当成高维空间里的一个点,余弦相似度就是两个向量夹角的余弦值。
cosine(u, v) = (u·v) / (|u|·|v|)它关注的是方向而不是长度。在用户对物品的评分矩阵里,一个用户对所有物品都打高分、另一个用户对所有物品都打低分,他俩的评分尺度不同,但偏好趋势可能一致,余弦相似度能把这个共性抓出来。
皮尔逊相关系数是余弦相似度的去中心化版本,计算时先减去用户的平均分:
pearson(u, v) = Σ((u_i - ū) * (v_i - v̄)) / sqrt(Σ(u_i - ū)² * Σ(v_i - v̄)²)去中心化之后,评分尺度差异被消除了。一个习惯打 4-5 分的用户和一个习惯打 2-3 分的用户,只要对共同物品的相对评价一致,皮尔逊相关系数就会很高。它在评分数据完整、数值有意义的时候表现比余弦好,尤其在电影评分、商品评分这类场景。
Jaccard 相似度适用于只有“买/没买”“点/没点”这类 0-1 行为的数据:
jaccard(u, v) = |N(u) ∩ N(v)| / |N(u) ∪ N(v)|分子是两人共同产生过行为的物品数,分母是两个人物品集合的并集大小。它完全不管行为的强弱,只看集合重叠程度,好处是计算简单、对稀疏数据相对鲁棒,坏处是信息利用不充分,两个人都买过同一件热门商品,不一定代表偏好一致,但 Jaccard 会给它们很高的相似度。
2.3 为什么这些公式决定了两者的行为差异
如果你把支持度、置信度、提升度和协同过滤的相似度公式放在一起看,会发现一个关键差异:关联规则处理的是一阶共现,协同过滤处理的是二阶关系。
具体来说,关联规则只看“A 和 B 有没有一起出现在订单里”,它不关心用户除了 A、B 之外还买了什么,也不关心其他用户的整体画像。这使它非常擅长发现“可并行的商品组合”,但也让它完全无法做个性化排序。因为对于同一个商品 B,关联规则给所有用户推的理由都一样:因为你买了 A,而 A 和 B 强关联。它能解释“为什么推 B”,却不能解释“为什么给你推 B、给他推 C”。
协同过滤则相反。它计算的是“用户-物品矩阵”里的行与行、列与列的关系,每个用户都有自己独立的向量,相似用户集合因人而异。这使它天然输出个性化列表,但代价是计算复杂度高、数据稀疏时相似度不可靠。
所以本质上,关联规则是“商品视角”的算法,协同过滤是“用户视角”的算法。视角不同,适用的数据规模、业务场景、工程实现就完全不同。
3. 应用场景:什么时候用谁,混着用会怎样
3.1 关联规则的主场:超市、电商、线下零售
关联规则最典型的应用场景是零售行业,因为它直接回答“货该怎么摆、券该怎么发、促销该怎么搭”这类问题。
我做过一个模拟的生鲜电商项目,数据是用户的下单流水。运营提的需求很明确:想把“买一送一”的促销商品和另一个关联商品绑定,提升客单价。这个需求里没有“个性化”三个字,目标就是找出整个大盘里最稳的组合,用关联规则再合适不过。最终跑出来一条很有意思的规则:“即食牛油果 -> 进口牛奶”的置信度相当高,因为很多买牛油果的用户是在做减脂餐,牛奶是搭配食材。运营直接把牛油果和牛奶做了套餐,转化率确实比单卖高。
这类场景的共同特征是:商品总数相对可控,订单数据量大,且业务方需要的是“可解释的规则”而不是“黑盒的个性化列表”。把关联规则的结果直接交给运营,运营能理解、能落地、能判断规则是否合理,这是它最大的产品优势。
3.2 协同过滤的主场:内容平台、视频、新闻、社交
协同过滤在主流的互联网内容平台里几乎是标配。因为内容平台的核心诉求是“每人看到不同的首页”,关联规则推不出这种个性化效果。
比如一个视频平台,用户看了 A 视频,系统要给他推下一个视频。如果用关联规则,系统只能找“看完 A 还经常看什么”的统一规律,所有看完 A 的人得到的推荐都一样,时间一长用户体验会非常单调。用基于物品的协同过滤,系统会找与 A 行为相似的一批视频,再结合用户自己的历史行为微调排序,每个用户得到的列表是独一无二的。
协同过滤也分两种用法:
- 用户量大、物品量相对少,比如招聘、婚恋场景,用基于用户的协同过滤找“相似的人”更自然。
- 物品量大、用户行为稀疏,比如电商、视频场景,用基于物品的协同过滤更稳,因为物品之间的相似关系比用户之间的相似关系稳定得多。
3.3 一张表看懂场景选择
| 维度 | 关联规则 | 协同过滤 |
|---|---|---|
| 核心输出 | 规则 / 商品组合 | 个性化推荐列表 |
| 个性化程度 | 无个性化,一条规则服务所有人 | 强个性化,每人生成独立结果 |
| 核心数据 | 订单 / 购物篮 | 用户-物品行为矩阵 |
| 冷启动能力 | 新用户也能推,规则不依赖用户历史 | 新用户无行为数据,无法推荐 |
| 可解释性 | 很好解释,天然面向业务 | 较差,需要额外做推荐理由 |
| 计算成本 | 低,商品组合规模可控 | 高,矩阵运算和近邻计算开销大 |
| 典型场景 | 零售捆绑、促销组合、货架陈列 | 内容Feed流、电商猜你喜欢、视频推荐 |
这张表不是说两个算法互斥,而是说你得根据业务目标选主算法。目标如果是“提升客单价、做捆绑销售”,关联规则是主线;目标如果是“让每个用户看到不同的内容”,协同过滤是主线。
3.4 场景混搭的真实案例
实际项目中,我见过一个很有意思的混搭案例。模拟项目里有个家居电商,商品分成高客单价低频次的大件和低客单价高频次的小件。
大件的购买决策周期很长,用户几个月才买一次,协同过滤在这种数据上表现很差,因为行为矩阵太稀疏了。小件则相反,用户反复购买,协同过滤效果很好。
最终方案是分层处理:小件走协同过滤做个性化推荐,大件走关联规则做组合推荐。比如用户买了一个书架,协同过滤推不出什么,因为历史上买书架的人太少;但关联规则能从所有包含书架的订单里发现,“书架 + 桌面收纳盒 + 阅读灯”经常一起出现,于是把这三件做成套餐推荐。两种算法各自解决各自的问题,效果比硬把它们放在同一个模型里比拼好得多。
4. 实操笔记:小型推荐系统的选型与实现
4.1 数据准备:把行为日志整理成两种格式
无论用哪种算法,第一步都是把原始行为数据整理成算法能吃的样子。原始日志长什么样?通常是一张流水表,每一行是一次行为事件,字段包括用户ID、物品ID、行为类型、时间戳。
关联规则需要的是“购物篮格式”的订单数据。每一行代表一个订单,里面是所有商品的集合:
order_id, items 1001, [牛奶, 面包, 鸡蛋] 1002, [牛奶, 啤酒] 1003, [面包, 鸡蛋, 果酱]协同过滤需要的是“用户-物品三元组”,每一行是“用户、物品、行为强度”:
user_id, item_id, rating u001, i001, 5 u001, i002, 1 u002, i001, 4如果数据源是购买流水,可以直接从订单表里派生这两种格式。如果行为数据是隐式的,比如只有点击数据,没有评分,就需要自己定义“行为强度”。常见的做法是给不同行为赋权,比如点击算 1 分、收藏算 2 分、加购算 3 分、购买算 5 分,然后按用户-物品分组求和,得到伪评分。
这一步看似简单,但实际上决定了整个项目能不能做。我踩过一个坑:某次项目里只用购买行为生成矩阵,结果大量用户只有一两笔订单,矩阵稀疏度到了 99.7%,协同过滤形同虚设。后来把浏览、收藏行为全部纳入,构造了多层级的伪评分,矩阵覆盖率和推荐效果才有了明显提升。数据预处理永远是推荐系统里的隐形决定因素。
4.2 用关联规则跑出商品组合
关联规则的挖掘过程,最朴素的实现是穷举所有商品组合,但商品数量一多就爆炸了。所以实际用的是 Apriori 或 FP-Growth 这类剪枝算法,核心思想都一样:如果某个商品组合的支持度已经低于阈值,那么任何包含它的更大组合支持度也不可能超过阈值,直接剪掉。
在代码层面,你可以用现成的数据挖掘工具库,也可以自己写核心逻辑。下面是用伪代码写的大致流程:
# 伪代码:关联规则挖掘流程 orders = load_orders() # [{order_id, items: [...]}] # 1. 计算单个商品的支持度 item_support = {} for order in orders: for item in order.items: item_support[item] += 1 total_orders = len(orders) # 2. 设置最小支持度阈值,筛掉长尾商品 min_support = 0.01 frequent_items = { item: count / total_orders for item, count in item_support.items() if count / total_orders >= min_support } # 3. 生成候选二项集,再计算支持度 pair_counts = {} for order in orders: items = [i for i in order.items if i in frequent_items] for i in range(len(items)): for j in range(i + 1, len(items)): pair = (items[i], items[j]) pair_counts[pair] += 1 # 4. 筛选并计算规则指标 rules = [] for (a, b), count in pair_counts.items(): support = count / total_orders confidence = count / item_support[a] lift = confidence / (item_support[b] / total_orders) if confidence >= min_confidence: rules.append({ "antecedent": a, "consequent": b, "support": support, "confidence": confidence, "lift": lift })实际生产我用的是现成封装好的函数,但原则是一样的。关键不是实现,而是调参数:
min_support设得太高,会漏掉长尾商品的组合;设得太低,会产生大量噪声规则,运行时间也变长。min_confidence通常设在 0.3~0.5 之间,具体看业务。捆绑促销要求高置信度,品类推荐可以放宽。lift是筛选有效规则的核心,我一般要求lift > 1.2,否则这条规则对业务没什么增量。
还要注意规则的方向性。A -> B和B -> A的支持度一样,但置信度不同,因为分母一个是 support(A)、一个是 support(B)。业务上要先确定“谁是诱饵,谁是目标”。促销里通常把流量大的商品放左边,把想要带动销量的商品放右边。
4.3 用协同过滤跑出个性化TopN
协同过滤的实现也可以走简化路线。以基于物品的协同过滤为例,完整流程分三步:
第一步,构造用户-物品矩阵。行是用户,列是物品,值是行为强度。
第二步,计算物品两两之间的相似度。这里有个关键点:在实际计算时,不会对全量物品两两计算,而是只对“在同一用户行为记录里共现过的物品对”计算相似度。没有共现关系的物品相似度本来就是 0,算它纯属浪费资源。
第三步,对某个用户生成推荐。拿到用户历史行为过的物品集合,找出这些物品的相似物品,按相似度加权汇总,排除用户已经买过的物品,排序取 TopN。
伪代码如下:
# 伪代码:基于物品的协同过滤 matrix = build_user_item_matrix() # user_id -> {item_id: rating} def recommend(user_id, top_k=10): user_items = matrix[user_id] # 该用户互动过的物品集合 # 累加每个候选物品的推荐得分 scores = {} # item_id -> 累计得分 for item, rating in user_items.items(): for similar_item, sim_score in item_sim[item].items(): if similar_item in user_items: continue # 排除已经买过的 scores[similar_item] += rating * sim_score ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True) return [item_id for item_id, score in ranked[:top_k]]这段逻辑很简单,但里面藏着一个重要细节:rating * sim_score的乘法是什么意思?它表示“用户对历史物品的偏好程度”乘以“历史物品和候选物品的相似程度”。用户越喜欢的物品,它带来的相似物品推荐权重就越高。这个加权方式就是协同过滤的灵魂。
相似度的计算方式也会直接影响结果。如果你发现推荐的物品总是集中在几个热门品类里,很可能是因为热门物品的共现次数太高,相似度虚高。解决方法是把相似度做归一化,或者用皮尔逊相关系数代替余弦相似度。更稳妥的办法是在相似度计算时对热门物品做降权,比如用1/log(1 + item_流行度)做权重。
4.4 评估对比:离线指标怎么选
两种算法都跑完了,怎么判断谁更好?很多新人上来就盯着准确率,这是个误区。推荐系统里,准确率并不是唯一重要的指标,甚至很多时候不是最重要的指标。
关联规则的评估,最直接的是看规则质量:
- 规则的支撑面够不够大(支持度)。
- 的确认程度高不高(置信度)。
- 是否比随机推荐更有效(提升度)。
- 业务上是否可解释、可落地。
协同过滤的评估,离线阶段通常看三组指标:
- 精确率与召回率:把用户行为数据切成训练集和测试集,用训练集建模,在测试集上看推荐结果命中了多少比例。精确率看“推的里面有多少是对的”,召回率看“用户真正看过的里面有多少被推到了”。
- 覆盖率:推荐系统一共推荐了多少不同物品。如果只推荐头部热门,覆盖率会很低,说明推荐多样性差。
- 多样性/惊喜度:推荐结果里品类分布是否均匀。这个指标业务上很关键,但离线计算时会稍微复杂一些,需要定义物品之间的内容距离。
我在实际项目里最常用的对比方式很朴素:把用户分成两组,一组线上走关联规则策略,另一组走协同过滤策略,观察点击率、加购率、客单价这些业务指标。离线指标只能帮你筛掉明显不好的方案,最终拍板的永远是线上业务结果。
5. 常见问题与避坑心得
5.1 冷启动:谁更适合
冷启动是推荐系统最常见的坑。新用户没有任何行为数据,协同过滤根本无从下手。你可能觉得关联规则也没好到哪去,但这里有个细节区别:
关联规则不需要用户历史。用户第一次进页面,只要他此时点击了任意一个商品,系统就能根据这个商品的关联规则立刻推荐搭配商品。这是一种“基于当前行为”的即时推荐,冷启动问题天然不存在。
协同过滤则比较尴尬。新用户没有行为历史,没有相似邻居,推荐列表一片空白。常见的处理办法是给新用户推荐热门榜,等用户积累行为后再切到协同过滤。所以如果你的新产品刚上线、用户行为数据还很稀疏,先用关联规则或者规则策略撑住冷启动阶段,并不是丢人的方案,反而很务实。
5.2 稀疏矩阵:为什么协同过滤容易失灵
协同过滤的天敌是稀疏性。现实场景里,一个平台可能有几百万物品,但用户和绝大多数物品都没发生过交互,每个用户向量里绝大部分都是 0。评价一下这种向量做余弦相似度,结果会很低,而且区分度差。
稀疏性带来两个问题:
一是相似度计算不稳定。两个用户如果只在一两个热门物品上有重合,算出的相似度可能很高,但不是真实偏好相似,只是大家都买了那个爆款,这个相似度噪声极大。
二是推荐的覆盖范围窄。协同过滤倾向于推荐热门物品,因为热门物品的共现概率高,但真正有价值的长尾推荐反而出不来。
缓解手段主要有降维、矩阵分解、引入物品内容特征做混合推荐。实际操作中,我建议先用“行为次数加权”的方式削弱热门物品的影响。比如共现矩阵里所有权重除以log(1 + 物品出现次数),能显著压制热门物品的相似度,让长尾物品有机会浮上来。
5.3 实时性:两个算法的线上成本
线上推荐系统对实时性的要求分两种:一是“秒级响应”,用户请求来了要立刻给结果;二是“分钟级更新”,用户最新行为要快速反映到推荐里。
关联规则在实时性上有个天然优势:规则是离线算好的,线上只做查询匹配。用户刚买了一个商品,系统立刻能在规则表里查到关联商品并返回,耗时通常不到一毫秒。缺点是规则更新频率低,新出现的消费趋势不能马上反映到推荐结果里。
协同过滤的实时更新则复杂得多。基于物品的协同过滤如果想反映用户最新行为,最简单的方案是每次请求时实时算推荐得分,但当物品数量大时,这个方法计算量非常大。常用的优化方案是分两段:物品相似度表离线更新,线上只做“用户行为乘以相似度”的聚合计算。这样线上负载可控,最新行为也能即时影响推荐结果。基于用户的协同过滤就更麻烦一点,因为用户相似度矩阵的更新成本更高,很多平台已经把它替换成了矩阵分解或向量召回的方案。
5.4 常见问题速查表
| 问题 | 现象 | 排查思路 |
|---|---|---|
| 关联规则跑出一堆无意义组合 | 置信度不低但提升度接近1 | 提升度是判断有效性的关键,把 lift<1.2 的规则过滤掉 |
| 关联规则组合过于集中 | 规则里永远是那几个头部商品 | 降低最小支持度阈值,或对商品按品类、价格带分组后分组挖掘 |
| 协同过滤对老用户有效、新用户无效 | 新用户推荐列表为空 | 给新用户走热门榜或关联规则策略,积累行为后再切换 |
| 协同过滤推荐结果全是热门物品 | 覆盖率低、个性化弱 | 相似度计算时对热门物品做降权,引入长尾商品奖励权重 |
| 用户量很大时基于用户的协同过滤跑不动 | 计算时间成倍增长 | 改成基于物品的协同过滤,或者用聚类先分组再算相似度 |
| 行为数据全是点击、没有购买 | 评分矩阵缺失 | 用“点击1分、加购3分、下单5分”的方式构造伪评分 |
这些经验都是我从一次又一次改Bug、调参数、看线上数据的过程中攒下来的。尤其是“协同过滤推出来全是热门”这个问题,几乎每个入门者都会碰上一次。它表面上看起来推荐效果还不错,因为热门商品本身点击率就高,但你对比一下个性化覆盖率就会发现,用户实际看到的东西和他的真实偏好根本没有强关联。
还有一个小技巧,很多人容易忽略:不管用哪种算法,最终展示给用户的推荐结果都要做业务规则过滤,比如已购商品过滤、库存过滤、限售商品过滤。这一步虽然不属于算法逻辑,但对实际效果的影响经常比算法调参还大。我见过不止一次,模型离线指标刷得很漂亮,上线之后数据却不涨,原因就是推荐列表混进了用户已经买过的东西,把用户惹烦了。
我个人的体会是,不要神化任何一个算法。关联规则和协同过滤都是很老的技术,但它们至今没有被淘汰,说明各自都有不可替代的位置。选型的时候多问自己一句:这个业务到底是要“让同一类人看到更合适的东西”,还是要“让所有人看到一个更合理的组合”?答案清楚了,算法自然也就选对了。