十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Apriori算法Python源码解析:从频繁项集到关联规则

Apriori算法Python源码解析:从频繁项集到关联规则 简介这是一套Apriori关联规则算法的Python实现代码面向数据挖掘初学者、Python开发者以及需要开展购物篮分析、商品捆绑推荐等工作的读者。算法基于先验原理通过支持度与置信度筛选频繁项集和强关联规则代码完整涵盖数据预处理、候选集生成、支持度计算、置信度计算与剪枝优化等关键模块便于对照教材理论进行动手实践。资源包共5个文件zip压缩后仅3KB包含1个Python主程序、1个CSV示例交易数据集、README说明文档、LICENSE许可证及.gitignore配置文件结构非常精简下载后可直接运行或二次修改。该资源已有247人学习下载。通过阅读这段代码读者能够理解Apriori如何从事务数据中提取频繁项集、生成关联规则同时可以通过调整支持度与置信度阈值观察规则变化掌握数据集格式转换、函数模块拆分和结果验证等方法为后续实现FP-Growth等其他挖掘算法打下基础。1. 为什么还要读一遍Apriori源码而不是直接调mlxtend调mlxtend的apriori函数拿频繁项集代码不超过五步但真实业务数据一上min_support降到0.05时内存和耗时双双失控你才意识到自己根本不清楚它内部到底存了什么。这份python-apriori-master压缩包只带了两个核心文件apriori.py和example-simple.csv却把候选集生成、支持度计数、逐层剪枝的过程完完整整摊开了。它不是什么工业级框架而是一份能读懂、能改、能用来做课程设计的学习代码。如果你正在写数据挖掘作业或者想把关联规则用到电商订单日志里先花半小时把这份源码读透比反复调包更有用。整个项目只依赖Python标准库不需要额外安装第三方依赖解压后就能在VSCode里直接运行。2. Apriori核心支持度、置信度与逐层剪枝的Python化表达2.1 从交易数据到项集表示example-simple.csv该怎么读压缩包里的example-simple.csv不是常见的表格式CSV而是每行代表一笔交易商品之间用英文逗号分隔大概长这样A,B,C A,C B,D A,B,D这种格式在关联规则里叫交易数据库。读的时候要注意几个点文件末尾可能多一个换行直接用strip()去掉行内如果混入空格比如 A,B要用item.strip()处理否则A和 A会被当成两个不同的商品直接破坏项集的子集关系。我一般会这样实现加载def load_transactions(file_path): transactions [] with open(file_path, encodingutf-8-sig) as f: for line in f: line line.strip() if not line: continue items [item.strip() for item in line.split(,) if item.strip()] transactions.append(list(set(items))) return transactions逻辑说明line.split(,)把一行拆成商品列表列表推导式里的if item.strip()用来过滤空字符串和纯空白商品list(set(items))去掉同一笔交易里的重复商品。参数file_path传example-simple.csv的路径即可。这里有一个容易忽略的细节set会改变商品顺序但后续所有项集操作都基于集合顺序无关紧要如果业务需要保留购买次数就不要用set去重而是把原始item列表直接append。提示在Windows上用Excel另存的CSV默认是GBK编码直接把encoding改成gbk才能读否则会报UnicodeDecodeError。改成utf-8-sig会同时兼容BOM头。这一小段数据加载是整个Apriori实现中最容易出错的位置最常见的现象是结果里出现 A和A两个单一项集根因就是漏了strip()。如果example-simple.csv是从某个数据库导出的还可能带有表头如果是表头行需要额外跳过第一行一般可以用next(f)跳过。2.2 支持度计算与剪枝条件为什么Ck到Lk要先筛一遍Apriori算法名字源自先验性质如果一个项集是频繁的那么它的所有子集都频繁反过来如果一个项集不频繁它的所有超集一定不频繁。这个性质的价值在于剪枝——不用为每个候选超集都扫描一遍全表。具体做法是从候选Ck筛Lk时先检查该候选的所有k-1子集是否都出现在上一轮的频繁项集Lk-1里只要有一个不满足就直接丢弃省下大量支持度计算。支持度公式是support_count(itemset)/len(transactions)Python里用集合运算实现最直观def support(transactions, itemset): itemset frozenset(itemset) count 0 for trans in transactions: if itemset.issubset(trans): count 1 return count / len(transactions)参数说明transactions是之前load_transactions返回的集合列表itemset可以是list、set或tuple函数内部统一转成frozenset这样它可以作为字典的key。这个方法在频繁项集数量少时没问题但每一轮过滤都要遍历多个候选每次遍历又要扫描全表性能瓶颈很明显。优化做法是提前把每个候选的支持度计数累加在一个字典里一轮只扫一次交易代码常见为def calc_support_count(candidates, transactions): counts {cand: 0 for cand in candidates} for trans in transactions: trans_set set(trans) for cand in candidates: if cand.issubset(trans_set): counts[cand] 1 return counts这里用Ck的候选集合作为外层循环内层做子集判断最终返回每个候选的计数。注意支持度阈值要转换成计数再比较避免反复做除法min_count int(min_support * len(transactions))然后直接判断counts[cand] min_count。如果min_support设为0.2100笔交易对应的min_count就是20这个转换在apriori.py里通常会出现因为整数比较比浮点数快也能避免浮点精度带来的边界问题。2.3 代码骨架apriori.py里的核心函数拆解把apriori.py拆开看常见的组织方式如下表这也是我推荐初学者从这份代码入手的理由函数职责清晰没有复杂依赖也不需要理解任何框架抽象。函数名职责核心参数load_transactions读取CSV交易数据file_pathcreate_C1生成候选1项集transactionsapriori_gen由Lk生成Ck1并剪枝Lk, kfilter_by_support按min_support过滤候选candidates, min_countgenerate_rules生成关联规则freq_sets, min_conf其中apriori_gen是体现Apriori剪枝的关键常见实现长这样from itertools import combinations def apriori_gen(Lk, k): Lk_list list(Lk) candidates set() for i in range(len(Lk_list)): for j in range(i 1, len(Lk_list)): l1 list(Lk_list[i])[:k-2] l2 list(Lk_list[j])[:k-2] if l1 ! l2: continue new_item frozenset(Lk_list[i] | Lk_list[j]) if len(new_item) ! k: continue subsets combinations(new_item, k-1) if all(frozenset(sub) in Lk for sub in subsets): candidates.add(new_item) return candidates逻辑说明两个长度为k-1的频繁项集如果它们的前k-2个元素相同合并结果就是长度为k的候选项集然后检查这个新项集的所有k-1子集是否都出现在Lk中不满足的剪掉。参数Lk是频繁项集的集合每个元素是frozensetk是目标项集长度。注意代码里的切片比较依赖项集内元素排序frozenset本身无序所以外部生成Lk时需要把每个项集统一转成sorted tuple否则合并会漏项。这个细节非常隐蔽我在不少课程设计代码里都看到过最终解决方法是把所有项集表示为sorted(frozenset)的tuple再整体放进set里。3. 从频繁项集到关联规则置信度、提升度与规则过滤3.1 规则生成时的组合爆炸与控制频繁项集本身并不能直接指导决策真正有业务意义的是形如啤酒 - 尿布的关联规则。从频繁项集生成规则时一个包含n个项的项集可以产生的非空真子集有2^n-2种所以规则数量随项集长度指数增长。比如一个5项的频繁项集它能衍生出30条规则全部算一遍再过滤计算量非常可观。控制组合爆炸的办法通常有两个一是限制最大频繁项集长度比如超过4项就不参与规则生成二是用置信度阈值提前剪枝生成一条规则就立刻判断不满足就不加入结果列表避免把所有规则先存下来再过滤。很多教学版apriori.py会直接递归所有子集在小数据上没有感觉但一旦真实数据里有长度6的频繁项集规则数就会翻到几百条。3.2 置信度、提升度阈值怎么设置信度公式是conf(A-B)support(A∪B)/support(A)它回答的是买A的人有多大比例买B。提升度liftconf/support(B)把B本身的热门程度排除掉。阈值设置没有标准答案完全取决于业务容错但可以按下面的表选切入点场景建议min_support建议min_conf是否看lift电商关联推荐0.01-0.050.5-0.7lift1金融产品交叉销售0.03-0.080.6-0.8lift1.5故障日志规则挖掘0.001-0.010.6-0.8是参数说明support过低会让候选集爆炸内存占用和运行时间会快速上升confidence过高会让规则只剩下纸巾-纸巾这类默认结论。所以我会先把阈值调得宽松跑通全流程再逐步收紧观察规则数量变化。比如从min_support0.2、min_conf0.5开始跑通后把min_conf提高到0.7对比规则数量下降了多少这个下降幅度可以帮助判断数据里的关联关系是普遍的还是集中在高频商品上。3.3 在apriori.py上做二次开发加入提升度输出很多课程设计版本只输出置信度不输出提升度。要在原代码上扩充可以在generate_rules函数里返回一个四元组。下面是改造后的版本也顺带解决了直接对原始交易扫描导致的性能问题from itertools import combinations def generate_rules(freq_sets, support_dict, min_conf): rules [] for itemset in freq_sets: itemset list(itemset) if len(itemset) 2: continue full_support support_dict[frozenset(itemset)] for r in range(1, len(itemset)): for ante_tuple in combinations(itemset, r): ante frozenset(ante_tuple) conf full_support / support_dict[ante] if conf min_conf: cons frozenset(set(itemset) - set(ante_tuple)) lift conf / support_dict[cons] rules.append((ante, cons, conf, lift)) return rules逻辑说明support_dict是以frozenset为键、支持度为值的字典这样不用每次重复扫描全部交易。min_conf是置信度阈值当conf满足条件时再算lift节省掉不需要的除法。参数freq_sets是经过filter_by_support输出的频繁项集集合可以是list或set。这里还隐藏一个关键点长度为2的频繁项集会生成A-B和B-A两条规则它们的conf和lift往往不同都要保留如果业务上只关心最终动作也可以在外面按lift排序后只取前N条。这个改造后的函数可以直接替换apriori.py里的同名字段替换时记得同时传入support_dict否则函数内部需要额外维护一个支持度字典。4. 跑通压缩包数据集格式、参数调整与常见报错排解4.1 example-simple.csv的格式与预处理要点不管是从课程设计里拿到的包还是从免费python源码大全里翻到的项目解压后都先不要急着运行。用VSCode打开文件夹确认Python解释器是3.6以上即可这个项目用到的集合推导和frozenset在低版本也能跑。然后打开example-simple.csv如果文件是从Excel导出的第一行可能会带上BOM头导致第一个商品变成\ufeffA。这种情况下load_transactions会把这个商品当成普通字符串最终结果里所有和A相关的项集都会消失而且控制台不会报错非常隐蔽。处理方式就是前面2.1节说的utf-8-sig编码或者用文本编辑器另存为UTF-8 without BOM。还有一类问题是数据里混有空行比如从数据库导出的CSV末尾带多余换行。load_transactions里的if not line: continue能处理绝大多数情况但如果一行里只有逗号比如,,line.strip()后变成,,不满足空行条件拆出来的列表是[, , ]。这种情况应该把过滤条件写在列表推导式里标准写法是items [item.strip() for item in line.split(,) if item.strip()]这里用列表推导式把空商品名过滤掉代价是如果商品名本身是空白字符就会被忽略在上游数据里这种情况应该提前清洗掉。另外如果CSV里商品名包含逗号比如洗发水, 沐浴露这种被强拆的情况说明原始数据结构有问题建议回到导出环节处理好字段分隔再来做关联分析。4.2 最小支持度、最小置信度参数怎么调跑这个包的方式取决于apriori.py里是否封装了main入口。我见过的版本有两种一种接受命令行参数像下面这样python apriori.py example-simple.csv --min_support0.2 --min_conf0.6另一种没有main直接在文件底部写死阈值。如果是后者改配置时只需要在文件末尾找到min_support、min_conf这两个变量改成浮点数即可。阈值调整对输出规模影响很大下面这组对照可以作为方向参考min_support频繁项集数量变化规则数量变化建议用法0.4很少几乎为0快速验证流程0.2适中可读默认起步值0.05爆炸式增长大量慎重使用注意内存参数说明改动阈值后如果输出为空不要急着怀疑代码有bug先检查当前数据里最大的单一项支持度是多少。比如只有3个商品的交易占20%你设0.5所有项集都会被剪掉。这个逻辑可以用一句Python确认from collections import Counter item_counts Counter() for trans in transactions: for item in set(trans): item_counts[item] 1 max_item_support max(item_counts.values()) / len(transactions) print(max_item_support)代码用Counter统计每个商品出现在多少笔交易里然后除以交易总数得到最大单项支持度。如果max_item_support小于你设定的min_support结果为空是正常的。注意这里对每笔交易做了set(trans)去重避免同一笔交易里重复商品把计数拉高。注意min_support0.05表示5%不是5条数据。如果数据只有20行0.05对应最小支持度计数1等于没有任何过滤效果。4.3 运行时常见的几个坑类型转换、空行、稀疏矩阵把常见报错和现象整理成一张排错表基本能覆盖80%的运行问题报错或现象原因处理方式TypeError: unhashable type: list用list作为集合或字典的key项集一律转成frozensetUnicodeDecodeError文件编码不是utf-8读文件时指定encodinggbk或utf-8-sig结果为空min_support高于所有项集支持度调低阈值或先打印最大单项支持度内存占用飙升把交易数据转成稠密矩阵改用list of frozenset不要pivot成DataFrame规则数量远多于预期没对重复规则去重对规则中的项排序后加入结果集合这里重点说内存问题。Apriori算法本质上是为中小规模数据集设计的当你把商品ID做成10000列0/1矩阵时内存已经被矩阵占掉大部分剩下的留给频繁项集的字典所剩无几。正确做法是始终用transactions的原始形式也就是list of frozenset每个frozenset只存储出现过的商品稀疏数据下内存开销小一个数量级。如果你发现规则结果里出现明显不合理的组合比如前件和后件完全一样多半是代码里没有做前件非空且后件非空的校验检查generate_rules里是否漏了len(ante_tuple) 0 or len(ante_tuple) len(itemset)这类判断。这类问题在example-simple.csv上不会出现因为示例数据太干净我只在被替换成真实订单数据时见过。5. 验证自己写的Apriori与mlxtend结果对照的小技巧5.1 用mlxtend跑同一份数据确认自己补全的apriori.py没有算错最好用现成库对照。mlxtend的接口要求输入是one-hot的DataFrame用TransactionEncoder转换只需要几行代码import pandas as pd from mlxtend.preprocessing import TransactionEncoder from mlxtend.frequent_patterns import apriori, association_rules transactions [[A, B, C], [A, C], [B, D], [A, B, D]] te TransactionEncoder() df pd.DataFrame(te.fit(transactions).transform(transactions), columnste.columns_) freq apriori(df, min_support0.2, use_colnamesTrue) rules association_rules(freq, metricconfidence, min_threshold0.6) print(rules[[antecedents, consequents, support, confidence, lift]])逻辑说明TransactionEncoder会把交易列表转换成每列一个商品、每行一笔交易的0/1矩阵apriori计算频繁项集association_rules生成规则。参数min_support必须和你自定义实现里的阈值一致metric和min_threshold对应自己的min_conf。这里用use_colnamesTrue让结果直接显示商品名便于和apriori.py的输出对齐。如果你手头没有mlxtend也可以用pip install mlxtend安装这个库只依赖pandas在VSCode的Python环境里装好就能跑。5.2 对比结果时的三个注意事项第一支持度在两边都是比例数值能够完全对上第二mlxtend会把项集内部按字母排序而你的frozenset输出顺序可能不一样比较前先转成set第三mlxtend会额外输出leverage、conviction等列忽略这些列即可。如果想快速判断两者规则集合是否一致可以在两个脚本里各print一条规则再排序后diffpython my_apriori.py my_rules.csv python mlxtend_rules.py mlxtend_rules.csv diff (sort my_rules.csv) (sort mlxtend_rules.csv)如果没有输出说明两边结果一致。如果diff有输出优先排查上一章提到的规则生成是否缺少长度判断以及前件后件的set计算是否正确。如果mlxtend版本较老association_rules里可能没有lift列先打印rules.columns查看字段再决定用什么列做对齐。这套对照方法不仅适用于原始包验证也适用于你改完提升度、加入最大长度限制之后回归测试。按照这个流程走下来你会确信自己手写的实现每一步都正确而不再是一份只能跑出结果、却不知道是否可信的代码。本文还有配套的精品资源点击获取
返回列表