news 2026/9/18 15:53:36

关联规则商品推荐:Apriori/FP-Growth与支持度置信度提升度

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
关联规则商品推荐:Apriori/FP-Growth与支持度置信度提升度

简介:这份PDF文档围绕电子商务场景下的商品推荐问题,整理了一套基于关联规则的商品推荐系统模型,面向数据挖掘初学者、电商技术研究者以及需要撰写相关论文或课程设计的高校学生。内容从推荐系统的基本作用讲起,梳理了简单关联、时序关联与因果关联的分类,并重点介绍Apriori算法、基于划分的算法和FP-树频集算法三种频繁项集挖掘思路,随后给出从数据采集、预处理到规则库建立、模型引擎计算的完整推荐流程,附有模型结构示意图与Apriori算法的伪代码描述。资源包仅含1个PDF文件,约118KB,篇幅精炼,便于快速通读与重点查阅,适合作为关联规则学习与电商推荐模型搭建的入门参考。目前已有102人学习,读者可借此理清频繁项集、支持度与可信度等核心概念,掌握将关联规则落地为推荐列表的整体思路。

1. 关联规则做商品推荐,为什么在小数据量下依然打得过深度模型

一个日订单几千单的垂直电商,算法同学上来就搭双塔召回,训了两个月,线上 CTR 还没跑过运营手工配的"买了 A 的人还买了 B"。这类场景里,一瓶啤酒和一包纸尿裤之间的关系不需要 128 维向量去表达,一条{啤酒} → {纸尿裤}、支持度 2.1%、置信度 34%、提升度 2.8 的规则就够用了。关联规则挖掘(Association Rule Mining)本质是在事务数据集里找出「同时出现频率显著高于随机」的项集组合,再用置信度、提升度把它翻译成可执行、可解释、可运营干预的推荐理由。它不需要 GPU,增量成本低,冷启动商品只要被买过一次就有机会进规则,SKU 在几千到几万、订单在百万级以下的业务里性价比极高。这里要讲的是:怎么把一张订单明细表变成一个能上线的商品推荐模型,支持度/置信度/提升度这三组参数怎么定,规则怎么打分进召回通道,以及线上最容易在哪儿翻车。

2. 关联规则的三组核心参数:支持度、置信度与提升度怎么定

关联规则的全部可调空间几乎都压在三个指标上。很多人第一次跑apriori就卡在这里:min_support 设 0.1 出来几十条规则,设 0.001 出来几十万条,然后随手挑了几条看着顺眼的就上线了。这三个参数各自控制的是不同阶段,必须分开定,不能一起拍脑袋。

2.1 支持度:决定候选集规模的第一个阀门

支持度描述的是"这个组合在所有订单里出现的概率",分母是订单总数(去重后的 transaction 数),不是商品件数。

support(X) = 包含项集 X 的订单数 / 订单总数

这个定义看起来简单,但分母搞错的人非常多:把明细行数当分母,算出来的支持度会系统性偏小,因为你把同一订单里的多行商品都算成了独立事务。正确做法是先按 order_id 聚合成"一单一个商品集合"。

支持度的量纲直接决定候选集大小。假设有 5000 个活跃 SKU,理论上的二元组合是 1250 万,Apriori 靠反单调性剪枝把它压下来,但压到什么程度完全取决于 min_support:

业务规模单量级min_support 建议区间预期频繁项集量级
垂直小店日均 < 1 千单0.005 ~ 0.0210^3 ~ 10^4
中型电商日均 1 万单0.001 ~ 0.00510^4 ~ 10^5
大促期间全量日均 > 50 万单0.0002 ~ 0.00110^5 ~ 10^6

我的习惯是先跑一遍支持度分布,再回推阈值:把商品对按共现次数排序,看第 5000 对、第 20000 对分别落在哪个支持度上,取那个点作为 min_support。这样得到的规则量是可预期的,而不是"跑完再删"。

注意:min_support 每下降一半,频繁项集数量通常增长 3~5 倍,内存占用随之上升。用 8G 内存的机器跑 0.0005 以下的支持度之前,先确认一下数据规模。

2.2 置信度与提升度:把"买了又买"和"顺便买"分开

置信度回答的是"买了 X 的人里有多少买了 Y":

confidence(X → Y) = support(X ∪ Y) / support(X)

它有个致命缺陷:如果 Y 本身是爆品,买什么都可能顺手带上它,置信度天然就高。比如"矿泉水"在 40% 的订单里出现,那{任意商品} → {矿泉水}的置信度都不会低于 0.2,这种规则没有任何推荐价值。

提升度修的就是这个问题:

lift(X → Y) = confidence(X → Y) / support(Y)

lift = 1 表示 X 和 Y 相互独立,lift > 1 表示正相关。经验上,我一般把 lift 阈值卡在 1.2~1.5 之间:

  • lift < 1.2:过滤掉,基本是噪声
  • 1.2 ≤ lift < 3:主力规则池,稳定可解释
  • lift ≥ 3:强关联,但数量少,很多是同一系列商品(手机和手机壳),要人工看一遍

除了 lift,还有两个补充指标值得算上。Kulc 度量 =(confidence(X→Y) + confidence(Y→X)) / 2,它对称,适合判断双向关联强度;不平衡比 IR =|support(X) - support(Y)| / (support(X) + support(Y) - support(X∪Y)),IR 越接近 1 说明两个商品的热度差距越大,这时候的高置信度往往是热门商品带出来的假象。把lift >= 1.3IR <= 0.7组合起来过滤,规则池会干净很多。

2.3 用 Python 在订单表上跑通第一次频繁项集挖掘

数据从订单明细表出发,先把 order_id 和 item_id 两列抽出来,聚合、编码、挖掘、过滤四步走。下面是能直接抄的最小可运行版本。

import pandas as pd from mlxtend.preprocessing import TransactionEncoder from mlxtend.frequent_patterns import apriori, association_rules # 1. 读取订单明细,只需两列 orders = pd.read_csv("order_detail.csv")[["order_id", "item_id"]] # 2. 按订单聚合成事务集合,单笔订单内商品去重 baskets = orders.groupby("order_id")["item_id"].apply( lambda s: sorted(set(s.astype(str))) ).tolist() # 3. One-Hot 编码,得到 订单 x 商品 的布尔矩阵 te = TransactionEncoder() te_array = te.fit(baskets).transform(baskets) df = pd.DataFrame(te_array, columns=te.columns_) # 4. 频繁项集挖掘,max_len 控制规则长度 freq = apriori(df, min_support=0.002, use_colnames=True, max_len=3) print(f"频繁项集数量: {len(freq)}") # 5. 生成规则并按提升度过滤 rules = association_rules(freq, metric="lift", min_threshold=1.3) rules = rules[rules["confidence"] >= 0.15] rules = rules[rules["support"] >= 0.001] print(f"过滤后规则数量: {len(rules)}")

逐行说明几个容易踩的点。第 2 步的set(s)不能省,同一订单里同一个 SKU 买了 3 件只应算一次事务出现,否则支持度会被重复计数扭曲。第 4 步的max_len=3很关键:不限制长度时,Apriori 会一直挖到没有频繁项集为止,四元、五元项集在几万 SKU 上的组合爆炸足以把内存打满;推荐场景里三元规则已经很少用得上,因为前件太长会导致覆盖用户太少。

association_rules返回的 DataFrame 一定包含这几个字段,后面接推荐系统全靠它们:

字段含义典型取值
antecedents规则前件(frozenset){A, B}
consequents规则后件(frozenset){C}
support前件后件同时出现的订单占比0.0021
confidence前件出现时后件出现的概率0.238
lift相对随机共现的提升倍数2.41

导出成 CSV 时记得把 frozenset 转成字符串,不然下游读回来还要再解析一遍。我一般再加一列rule_id = "|".join(sorted(antecedents)) + "=>" + "|".join(sorted(consequents))作为主键,方便做规则的增量比对和版本管理。

3. Apriori 与 FP-Growth 的实现选择:从订单表到规则库

模型跑通之后马上会遇到第二个问题:换一批数据、换一个时间窗口,同样一份代码要跑两三个小时。这时候该考虑换算法了。Apriori 和 FP-Growth 解决的是同一个问题,但代价结构完全不同,选错了会白白浪费机器。

3.1 数据准备:把订单流水转成事务矩阵

进入算法之前,清洗环节决定了最终规则的上限。几个常规动作:

  • 剔除退款、取消、测试订单,用一个order_status in ('paid', 'shipped', 'completed')的白名单,而不是黑名单
  • 剔除只买了一次的用户(可选)。这类用户的订单占总量可能到 40%,保留会稀释支持度
  • 商品粒度归一到 SKU,但在 SKU 极多时按"类目 + 品牌"上卷一层,得到更深、更稳的规则
  • 时间窗口取最近 90 天,大流量业务取 30 天,因为商品生命周期在缩短

清洗完之后,把数据落成两列格式(order_id, item_id),每行是一个订单-商品对。这个中间态建议用 Parquet 存,一方面列存读得快,另一方面后续做增量更新时可以按日期分区,只重跑最近 N 天的数据。

3.2 Apriori 逐层剪枝的实现细节与性能拐点

Apriori 的核心是反单调性:如果一个项集是频繁的,那么它的所有子集也是频繁的。反过来,如果一个项集不频繁,那么它的所有超集都不频繁。这条性质让算法可以按长度逐层推进:

  1. 扫描一次数据,统计所有单项的支持度,得到 L1
  2. 由 L1 两两连接生成候选二元项集 C2,再扫一次数据计数,剪掉不满足 min_support 的,得到 L2
  3. 由 L2 生成 C3,剪枝,计数,得到 L3
  4. 重复直到某一层为空

每一层都要完整扫描一遍数据,这是它最贵的地方。在事务数为 100 万、频繁项集到 4 层时,我实测过大概要扫 5~6 遍全表,单机跑一次 40~90 分钟。

优化的抓手有三个。第一是降事务数,用采样或者缩短时间窗口;第二是提高 min_support,哪怕从 0.002 提到 0.003,候选集可能就少一半;第三是用更快的计数实现。mlxtend 的apriori内部已经用了 numpy 的布尔矩阵运算,比纯 Python 循环快得多,但它是单线程的。

注意:Apriori 生成的频繁项集结果里,同一长度的项集是按支持度降序排的,取 TopN 做规则时直接 head 就行,不要再排序一遍。

3.3 FP-Growth 建树与条件模式基,什么时候值得换

FP-Growth 换了个思路:不生成候选集,而是把数据压缩成一棵前缀树(FP-Tree),然后递归地在树上挖。

建树过程分三步:第一次扫描统计单项支持度,按降序排列每个事务里的商品;第二次扫描把每个事务按排序后的顺序插入树中,共享前缀路径只记一次,路径上的计数累加;同时维护一个头指针表,记录每个商品在树中出现的位置链。挖的时候从支持度最低的项开始,沿着它的节点链往上找所有祖先路径,得到"条件模式基",再在条件模式基上递归建树,直到路径为空。

换成 FP-Growth 只需要改一行:

from mlxtend.frequent_patterns import fpgrowth # 同样的 One-Hot 矩阵 df,只扫两遍数据 freq = fpgrowth(df, min_support=0.002, use_colnames=True, max_len=3)

参数含义和 apriori 完全一致,输出结构也一致,所以下游代码不用动。区别在代价:FP-Growth 只扫两遍数据,建树之后全在内存里递归,理论复杂度远低于 Apriori。

那是不是无脑换?不是。三个场景下 FP-Growth 反而更慢:

场景更优选择原因
事务数万级、SKU 百级Apriori建树和递归开销大于收益
min_support 很高(> 0.05)Apriori候选集本身就小,扫几遍无所谓
min_support 极低(< 0.0005)AprioriFP-Tree 分支爆炸,内存扛不住
min_support 低、SKU 中等FP-Growth典型的树形收益区间

实操上我一般这样做:拿一周数据分别用两个算法在 min_support = 0.002、max_len = 3 下跑一遍,比一下耗时和内存,选快的那一个写进调度。因为数据分布会变,这不是一次性决策,每隔一两个月值得重跑一次这个对比。

4. 把规则接到推荐系统:召回、排序与线上打分

规则库挖出来只是半成品。{A, B} → {C}(支持度 0.0021,置信度 0.238,提升度 2.41)这种三元组直接丢给推荐接口,会遇到两个问题:同一个用户触发了多条规则、后件重复,需要合分;规则是按支持度排序的,不是按业务收益排序的。这一章解决从规则库到推荐结果之间的那段路。

4.1 规则库落库与增量更新策略

规则表建议按这个结构建,字段都来自association_rules的输出,再补两个业务字段:

CREATE TABLE rec_assoc_rule ( rule_id VARCHAR(512) PRIMARY KEY, -- "A|B=>C" 形式,天然去重 antecedent VARCHAR(512) NOT NULL, -- 前件,多个用 | 分隔 consequent VARCHAR(128) NOT NULL, -- 后件,目前只保留单商品 support DECIMAL(10,6) NOT NULL, confidence DECIMAL(10,6) NOT NULL, lift DECIMAL(10,6) NOT NULL, kulc DECIMAL(10,6), imbalance DECIMAL(10,6), item_cnt INT, -- 前件商品数,2 或 3 version VARCHAR(16), -- 批次号,如 20240610 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_antecedent (antecedent), KEY idx_consequent (consequent) );

idx_antecedent这个索引是线上查询的生命线。推荐接口拿到用户购物车里的商品集合后,要把集合的所有子集去规则表里查前件,二元的 3 个商品会展开成 7 个子集(含单项),三元的会展开成 15 个。索引没建好,这里就是全表扫。

增量更新我一般用"滑动窗口 + 全量重算"的折中方案:每天凌晨用最近 90 天数据全量跑一次,生成新版本号,写进临时表;比对新旧版本的规则差异,只把新增和指标变化超过 20% 的规则合并进主表;旧版本保留 7 天用于回滚。为什么不纯增量?因为支持度是会衰减的,一条规则去年支持度 0.01,今年可能已经降到 0.001,纯增量只会不断往表里加规则,永远不删。

4.2 从规则到 TopN 推荐列表的打分公式

同一个用户可能命中十几条规则,后件还可能重复。这时候需要一个统一打分函数把候选排出来。我常用的形式是加权乘积:

import math from collections import defaultdict def score_candidates(hit_rules, alpha=0.6, beta=0.4, decay_lambda=0.05): """ hit_rules: 命中的规则列表,每条是 dict: {'consequent': 'C', 'lift': 2.41, 'confidence': 0.238, 'age_days': 12} alpha/beta: lift 与 confidence 的权重,两者之和不必为 1 decay_lambda: 时间衰减系数,越大衰减越快 """ scores = defaultdict(float) for r in hit_rules: # 时间衰减:规则越老,权重越低 decay = math.exp(-decay_lambda * r["age_days"]) # 核心打分项:提升度与置信度的加权几何平均 base = (r["lift"] ** alpha) * (r["confidence"] ** beta) scores[r["consequent"]] += base * decay # 按分数降序取 TopN ranked = sorted(scores.items(), key=lambda x: x[1], reverse=True) return ranked[:20]

几个参数的经济含义要说清楚。alpha 大于 beta 表示更看重"这条规则比随机强多少",适合新品曝光的场景;beta 大于 alpha 表示更看重"命中的概率有多大",适合转化率优先的场景。我一般从 alpha=0.6、beta=0.4 起调,在 AB 实验里对比 alpha=0.5/0.7 两组。

decay_lambda 控制规则的新鲜度。取 0.05 意味着一条 30 天前生成的规则权重衰减到 0.22,接近失效;取 0.01 则 30 天只衰减到 0.74。生鲜、服饰这类季节敏感品类建议 0.05~0.1,标准品、图书可以放宽到 0.01。

多个后件累加而不是取最大值,这个设计是有意为之的:一个商品被三条不同的规则同时指向,说明它在多个商品组合里都是合理的下一步,应该被推得更靠前。这是关联规则相比协同过滤的一个优势——它有天然的"理由叠加"。

4.3 与协同过滤、深度模型融合的边界

规则通道在任何推荐系统里都应该是召回层的一个通道,而不是排序层的最终决策。典型架构是:关联规则通道出 200 个候选,协同过滤出 200 个,热门兜底出 100 个,合并去重后交给粗排和精排。

如果非要在召回层就做融合,最稳妥的做法是给每个通道一个可调的加权分,权重用线上的 CTR 反推:

final_score = w_rule * norm(rule_score) + w_cf * norm(cf_score) + w_pop * norm(pop_score)

这里的 norm 建议用 min-max 归一化到 [0,1],因为三个通道的原始分数量纲完全不同,规则分可能都在 0~5 之间,协同过滤的余弦相似度在 0~1 之间。归一化之后权重才有意义。

那什么时候该用深度模型替代规则?我的判断标准是:当用户侧特征和上下文特征(时段、设备、来源渠道)对推荐结果的影响超过商品共现关系时。规则模型只能回答"买了 A 的人还买什么",回答不了"这个用户在晚上 10 点用手机访问时应该看什么"。但即便如此,规则通道通常也不需要下线——它是最好的冷启动兜底和新品曝光工具,一个没有任何行为数据的新 SKU,只要被买过几次,就能通过规则获得曝光,而深度模型要等它积累足够的样本才能学好它的 embedding。

5. 规则质量验证与线上效果排错:几个容易踩到的坑

规则模型的坑不在算法,在数据和评估口径。

5.1 用时间切分验证,避免规则穿越

最常见的错误是:用全量 90 天数据挖出规则,再用同一批 90 天数据评估命中率。这时候的指标好看得离谱,因为规则本来就是从这批数据里长出来的。

正确做法是严格按时间切:用第 1~60 天的订单挖规则,用第 61~90 天的订单做测试集,统计规则在测试集上的命中情况。评估指标至少看两个:

指标计算方式参考基线
HitRate@10测试集中,前 10 个推荐里命中真实下一单商品的比例8% ~ 20%
Coverage规则后件覆盖的 SKU 数 / 全部在售 SKU 数5% ~ 30%
Rule Recall测试集订单中,能被至少一条规则覆盖的比例15% ~ 40%

Coverage 太低说明支持度卡太狠,只有头部商品能进规则;太低的同时 HitRate 很高,说明规则池在自我循环,推荐结果会越来越集中在少数商品上,长期会损害长尾商品的曝光。

5.2 三类高频翻车:通用品、长尾品、季节性

第一类是通用品污染。塑料袋、纸巾、矿泉水、赠品这些商品几乎每单都有,它们出现在大量规则的右边,把真正有价值的推荐挤下去了。处理方式是给商品算一个逆文档频率(IDF)式的权重,或者直接建黑名单:把出现在 30% 以上订单里的 SKU 全部从后件中剔除。这个动作对规则池的清理效果通常立竿见影,规则数可能砍掉 40%,但 CTR 会涨。

第二类是长尾商品永远进不了规则。一个只卖了 20 次的 SKU,在 100 万订单里支持度是 0.00002,任何合理的阈值都捞不到它。解决办法是做粒度上卷,把长尾 SKU 归到"类目-品牌-价格带"这个组合上,用组合去挖规则,再在组合内部按销量排序选具体商品。这样规则的前件是具体商品,后件是"类目-品牌-价格带",推荐时再下一层。

第三类是季节性失效。夏季挖出的"防晒霜→晒后修复"在冬天就是噪声。除了在 4.2 的打分里加时间衰减,还可以按季度分别维护规则池,接口根据当前日期选版本。这个做法成本不高,但对服饰、食品、户外类目效果明显。

5.3 上线前跑一个拦截率检查

最后补一个我自己每次都会跑的检查:拿最近 7 天的真实订单,模拟"用前 3 个商品推荐第 4 个"的场景,统计规则给出的推荐里,有多少是用户已经买过的。这个比例叫已购拦截率,超过 30% 就说明规则池里大量是"同单共现"而非"跨单复购",推荐列表里会塞满用户刚买完的东西。处理方式是在打分函数里给已购商品乘一个 0.1 的惩罚系数,或者干脆在候选阶段就过滤掉近 30 天内已购的 SKU。这一步做完,规则的推荐列表才算真正可用。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 15:53:16

为什么说paperxie是真正的一站式论文工具?5个维度硬核证明

现在很多AI论文工具都自称"一站式"&#xff0c;但你仔细用就会发现&#xff0c;很多所谓的"一站式"其实是伪一站式——要么功能不全只能做某几个环节&#xff0c;要么各功能之间数据不互通要手动复制粘贴&#xff0c;要么流程不连贯上一个环节的产出不能直…

作者头像 李华
网站建设 2026/9/18 15:52:26

BACnet/IP跨网段通信:BBMD原理、配置与故障排查实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 15:52:19

VME总线嵌入式Linux:A24窗口、中断链路与BERR异常定位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 15:52:00

通达信国外MACD主图周线指标公式源码与共振过滤

简介&#xff1a;面向通达信软件使用者与金融技术分析爱好者的一份指标公式文档&#xff0c;重点在于把国外MACD思路搬到主图、并按周线周期做改造&#xff0c;用来判断趋势转折与多空动能变化。包内只有1个doc文件&#xff0c;体积约226KB&#xff0c;可直接用Word或WPS打开&a…

作者头像 李华
网站建设 2026/9/18 15:51:55

嵌入式高频协议考点:I²C与SPI的硬件级深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 15:50:42

嵌入式开发自学路线:从MCU到Linux驱动与边缘AI部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华