简介:这份资源是面向数据挖掘初学者与MATLAB使用者的FP-Growth关联规则挖掘实现包,用于在交易数据中高效发现频繁项集并提取关联规则,适合市场篮子分析、网络行为分析等场景的入门实践。压缩包共8个文件,以7个.m脚本和1个.mat数据集为主,整体约5KB,其中脚本承担主流程调度、FP树构建、幂集与非平凡子集生成、规则展示及树结构可视化等职责,mydata数据集则用于直接运行演示。已有191人学习下载。通过阅读与调试这些代码,读者可以完整走通从数据预处理、设定最小支持度、构造FP树、生成条件模式基到递归挖掘与规则提取的全过程,理解FP-Growth相较Apriori减少数据扫描次数的效率优势,并可将mydata替换为自己的事务数据,调整支持度阈值后快速迁移到实际挖掘任务中。
1. 从一份 FP-Growth 压缩包说起:matlab 关联规则挖掘到底能跑多快
如果你手头有一份名为FP-Growth Assiciation Rule Mining.rar的资源包,里面大概率是一套用 MATLAB 实现的频繁模式增长算法,用来做关联规则挖掘。它解决的核心问题很具体:给定一批事务数据(比如购物篮、日志事件、故障码组合),找出频繁共现的项集,再从中导出「A 出现时 B 也容易出现」这类规则。适合谁?做数据挖掘课程设计的学生、需要快速验证关联规则效果的分析人员,以及不想从零手写 FP-Tree 的 MATLAB 用户。很多人第一次接触关联规则是从 Apriori 开始的,但 Apriori 要反复扫描数据库,事务一多就慢得让人怀疑人生。FP-Growth 的思路是把数据库压进一棵 FP-Tree,只扫两遍,后续挖掘全在树上递归进行,速度提升非常明显。这份资源包的价值就在于:它把建树、条件模式基、递归挖掘这条链路用 MATLAB 串了起来,你拿到手就能替换自己的数据跑通,而不是对着伪代码发呆。
2. FP-Tree 构建与条件模式基:MATLAB 里怎么把事务压成一棵树
2.1 为什么选 FP-Growth 而不是 Apriori
Apriori 的逻辑是「逐层候选生成 + 全库验证」,每增加一个项集长度就要重新扫一遍事务表。假设你有 10 万条事务、平均每条 20 个项,Apriori 在候选集膨胀阶段会产生大量无效组合,I/O 和内存双重压力。FP-Growth 的做法完全不同:第一遍扫描统计每个项的支持度,过滤掉低于最小支持度的项;第二遍扫描把每条事务里的项按支持度降序排列,依次插入一棵前缀树。树的每条路径代表一个项集,节点上记录计数。这样数据库的共现信息被完整保留在树结构里,后续挖掘不需要再碰原始数据。
在 MATLAB 里实现时,常见做法是用嵌套结构体或 containers.Map 来表示节点。结构体字段包括项名、计数、父节点索引、子节点映射。用 Map 存子节点比数组查找更快,因为项名到子节点索引的映射是 O(1)。我一般会先把事务转成元胞数组,每个元胞是一个字符串数组,然后跑两遍循环完成建树。
2.2 建树的核心代码与参数说明
下面这段代码演示了 FP-Tree 节点的定义和插入逻辑,是这类资源包里最关键的骨架:
% 定义 FP-Tree 节点结构 % name: 项名 count: 计数 parent: 父节点索引 children: Map<项名, 子节点索引> function node = createNode(name, count, parent) node = struct('name', name, 'count', count, ... 'parent', parent, 'children', containers.Map()); end % 将一条事务插入 FP-Tree % transactions: 元胞数组,每个元素是字符串数组 % minSupportCount: 最小支持度计数阈值 function [tree, root] = buildFPTree(transactions, minSupportCount) % 第一遍:统计支持度 itemCount = containers.Map('KeyType','char','ValueType','double'); for i = 1:length(transactions) items = unique(transactions{i}); % 同一事务内重复项只计一次 for j = 1:length(items) if isKey(itemCount, items{j}) itemCount(items{j}) = itemCount(items{j}) + 1; else itemCount(items{j}) = 1; end end end % 过滤低频项,并按支持度降序排列 freqItems = keys(itemCount); freqCounts = cell2mat(values(itemCount)); validIdx = freqCounts >= minSupportCount; freqItems = freqItems(validIdx); freqCounts = freqCounts(validIdx); [~, sortIdx] = sort(freqCounts, 'descend'); freqItems = freqItems(sortIdx); rankMap = containers.Map(freqItems, num2cell(1:length(freqItems))); % 第二遍:建树 tree = createNode('root', 0, 0); root = 1; nodeList = {tree}; for i = 1:length(transactions) items = transactions{i}; % 只保留频繁项,并按支持度降序排列 validItems = items(cellfun(@(x) isKey(rankMap, x), items)); [~, order] = sort(cellfun(@(x) rankMap(x), validItems)); validItems = validItems(order); currentNode = root; for j = 1:length(validItems) item = validItems{j}; childMap = nodeList{currentNode}.children; if isKey(childMap, item) childIdx = childMap(item); nodeList{childIdx}.count = nodeList{childIdx}.count + 1; else newNode = createNode(item, 1, currentNode); nodeList{end+1} = newNode; childIdx = length(nodeList); childMap(item) = childIdx; nodeList{currentNode}.children = childMap; end currentNode = childIdx; end end tree = nodeList; end逻辑说明:第一遍循环统计每个项的出现次数,unique保证同一事务内重复项不重复计数。第二遍循环里,rankMap把项名映射到支持度排名,排序后依次插入。插入时如果当前节点的子节点 Map 里已有该项,就增加计数;否则新建节点并挂到父节点下。参数minSupportCount是绝对计数阈值,通常由最小支持度乘以事务总数得到。比如 1000 条事务、最小支持度 0.05,阈值就是 50。这个值设得太高会漏掉长尾规则,设得太低会导致树爆炸、递归深度过大。
2.3 条件模式基的提取方式
建完树之后,挖掘频繁项集需要从每个频繁项出发,沿着节点头表找到所有包含该项的路径,截取从根到该节点的前缀路径,形成条件模式基。在 MATLAB 里,如果没建头表,可以遍历所有节点,对每个频繁项收集其祖先路径。常见做法是维护一个headerTable,用 Map 把项名映射到节点索引列表。每次插入新节点时,把节点索引追加到对应项的列表里。提取条件模式基时,对每个节点沿parent指针向上走,直到根节点,把路径上的项和该节点的计数组成一条条件事务。
这一步的坑在于:路径上的计数应该取该节点的计数,而不是路径上各节点的计数。因为条件模式基代表的是「以该项为后缀」的事务子集,计数由后缀节点决定。很多手写实现在这里翻车,导致频繁项集计数偏大。
3. 从频繁项集到关联规则:支持度、置信度与提升度的 MATLAB 落地
3.1 递归挖掘频繁项集的实现
FP-Growth 的递归挖掘函数接收一棵 FP-Tree 和当前后缀项集,输出所有频繁项集。核心步骤是:对每个频繁项,提取条件模式基,用条件模式基建一棵条件 FP-Tree,如果条件 FP-Tree 不是单路径,就继续递归;如果是单路径,直接枚举所有组合。
% 递归挖掘频繁项集 % nodeList: 节点列表 headerTable: 项名到节点索引列表的映射 % minSupportCount: 最小支持度计数 suffix: 当前后缀项集 % freqItemsets: 输出频繁项集,每个元素是 struct('items', {}, 'support', {}) function freqItemsets = mineFPTree(nodeList, headerTable, minSupportCount, suffix, freqItemsets) items = keys(headerTable); for i = 1:length(items) item = items{i}; nodeIdxs = headerTable(item); % 收集条件模式基 condTransactions = {}; for k = 1:length(nodeIdxs) nodeIdx = nodeIdxs(k); count = nodeList{nodeIdx}.count; path = {}; p = nodeList{nodeIdx}.parent; while p ~= 0 && ~strcmp(nodeList{p}.name, 'root') path{end+1} = nodeList{p}.name; p = nodeList{p}.parent; end if ~isempty(path) condTransactions{end+1} = path; condTransactions{end+1} = count; % 用元胞存计数 end end % 构建条件 FP-Tree 并递归 newSuffix = [suffix, {item}]; support = sum(cell2mat(condTransactions(2:2:end))); freqItemsets{end+1} = struct('items', {newSuffix}, 'support', support); % 如果条件模式基非空,继续递归 if length(condTransactions) >= 2 % 这里需要把 condTransactions 转成事务列表重新建树 % 实际代码中会调用 buildFPTree 的变体 end end end逻辑说明:headerTable的每个键对应一个频繁项,值是该项在树中所有节点的索引列表。对每个节点沿父指针向上收集路径,路径上的项就是条件模式基。support是该后缀项集的总支持度计数。递归终止条件是条件模式基为空或只包含单条路径。参数minSupportCount在递归过程中保持不变,但条件 FP-Tree 的建树阈值仍然是它,因为条件模式基里的计数已经反映了原始事务的频次。
3.2 关联规则的生成与三个度量指标
频繁项集挖出来之后,生成关联规则就是枚举每个频繁项集的所有非空真子集,计算置信度和提升度。假设项集{A, B, C}的支持度是s,子集{A, B}的支持度是s1,那么规则{A, B} -> {C}的置信度是s / s1,提升度是s / (s1 * s_C / N),其中s_C是{C}的支持度,N是事务总数。
| 指标 | 公式 | 含义 | 调参建议 |
|---|---|---|---|
| 支持度 | count / N | 项集出现的频率 | 从 0.01 起步,数据稀疏时降到 0.005 |
| 置信度 | support(X∪Y) / support(X) | X 出现时 Y 也出现的概率 | 常用 0.5~0.8,太低规则不可信 |
| 提升度 | confidence / support(Y) | 规则是否比随机猜测好 | 大于 1 才有正相关,小于 1 说明负相关 |
在 MATLAB 里,可以用nchoosek枚举子集,但要注意项集长度超过 10 时组合数会爆炸。常见做法是只对长度不超过 4 的频繁项集生成规则,或者用剪枝策略:如果某个子集的置信度已经低于阈值,它的超集规则也不用算了。
3.3 规则筛选与排序的实操建议
跑完挖掘后,你可能会得到几千条规则。直接看根本看不过来。我一般会按提升度降序排,然后过滤掉提升度小于 1.2 的规则,再按置信度做二次排序。如果规则里出现「全集项」——也就是支持度等于事务总数的项,比如「购物袋」这种每条事务都有的项——要手动剔除,否则它会出现在大量规则的右边,毫无信息量。
另一个实操点是:对规则做分组。比如按右侧项分组,看每个目标项被哪些前置项触发。这在故障诊断场景里特别有用:你想知道「哪些故障码组合会导致某个严重故障」,按右侧项分组后一目了然。
4. 避坑与排查:MATLAB 跑 FP-Growth 最常见的五类翻车
4.1 支持度阈值设太低导致内存溢出
现象:程序跑到一半 MATLAB 报「Out of memory」,或者递归深度过大触发「Maximum recursion limit reached」。 原因:最小支持度设得太低,频繁项数量激增,FP-Tree 节点数膨胀,条件模式基的递归层数也跟着涨。 解决:先用支持度 0.05 跑一遍看频繁项数量,如果超过 5000 个,说明阈值太低。逐步提高到 0.1 或 0.2,观察规则数量变化。另一个办法是限制项集最大长度,在递归函数里加一个maxLen参数,超过就返回。
4.2 事务内重复项导致计数偏大
现象:某个项的支持度计数比实际事务数还大。 原因:建树时没有对同一事务内的项去重,同一个项在一条事务里出现两次就被计了两次。 解决:在统计支持度和插入树之前,对每条事务调用unique。注意unique会改变顺序,所以要在排序之前做。如果业务上重复项有意义(比如数量),那应该把项名和数量拼成一个新项,而不是让同一个项名出现多次。
4.3 条件模式基的计数取错节点
现象:频繁项集的支持度比预期大很多,规则置信度超过 1。 原因:提取条件模式基时,路径上每个节点的计数被累加了,而不是取后缀节点的计数。 解决:条件模式基里每条路径的计数应该等于该路径末端节点的计数。路径中间的节点计数代表的是包含更长后缀的事务数,不能直接用。检查代码里count变量是不是从nodeList{nodeIdx}.count取的。
4.4 项名排序不稳定导致树结构不一致
现象:同样的数据跑两次,得到的频繁项集数量不一样。 原因:支持度相同的项在排序时顺序不稳定,MATLAB 的sort对相同值的元素不保证原始顺序。 解决:在按支持度降序排列后,对支持度相同的项再按项名字典序排一次。这样每次建树的顺序完全一致,结果可复现。代码里加一行[~, idx] = sortrows([-freqCounts, freqItems], [1, 2])之类的二次排序。
4.5 规则生成阶段组合爆炸
现象:频繁项集挖完了,但生成规则时卡死。 原因:一个长度为 15 的频繁项集有 2^15 - 2 = 32766 个非空真子集,逐个计算置信度非常慢。 解决:限制频繁项集的最大长度,或者在生成规则前先按支持度过滤掉长项集。另一个策略是只生成右侧为单项的规则,这样组合数从 2^n 降到 n,速度快很多,而且大多数场景下单项规则更容易解释。
5. 进阶技巧:用 MATLAB 把 FP-Growth 封装成可复用函数
5.1 输入输出接口设计
把整个流程封装成一个函数,输入是事务元胞数组和参数结构体,输出是频繁项集表和规则表。参数结构体里放minSupport、minConfidence、maxItemsetLen、maxRuleLen。输出用 MATLAB 的table类型,方便后续筛选和导出。
function [freqTable, ruleTable] = fpGrowthMining(transactions, params) % 参数默认值 if ~isfield(params, 'minSupport'), params.minSupport = 0.05; end if ~isfield(params, 'minConfidence'), params.minConfidence = 0.6; end if ~isfield(params, 'maxItemsetLen'), params.maxItemsetLen = 4; end N = length(transactions); minCount = ceil(params.minSupport * N); % 建树与挖掘 [nodeList, headerTable] = buildFPTree(transactions, minCount); freqItemsets = mineFPTree(nodeList, headerTable, minCount, {}, {}); % 转成 table itemsStr = cellfun(@(x) strjoin(x, ','), ... cellfun(@(x) x.items, freqItemsets, 'UniformOutput', false), ... 'UniformOutput', false); supports = cellfun(@(x) x.support, freqItemsets); freqTable = table(itemsStr, supports, 'VariableNames', {'Itemset', 'Support'}); % 生成规则 ruleTable = generateRules(freqItemsets, params.minConfidence, N); end逻辑说明:minCount用ceil向上取整,避免支持度刚好等于阈值时被过滤掉。freqItemsets是元胞数组,每个元素是结构体,包含项集和支持度。转table时把项集用逗号拼成字符串,方便查看。generateRules是单独的函数,负责枚举子集、计算置信度和提升度、按阈值过滤。
5.2 用真实场景数据验证
拿一份超市购物篮数据测试:1000 条事务,平均每条 8 个商品,最小支持度 0.03,最小置信度 0.5。跑完之后看规则表,按提升度排序。如果发现「牛奶 -> 面包」这种规则提升度只有 1.05,说明两者几乎独立,不用关注。如果「啤酒 -> 尿布」提升度到 2.3,那就是强关联,值得深挖。
验证方法:手动抽几条规则,回到原始事务里数一下共现次数,和程序输出的支持度对一下。如果对不上,大概率是条件模式基的计数取错了,回到 4.3 排查。
5.3 性能调优的几个习惯
第一,把事务数据转成数值索引再建树,比用字符串快很多。做法是维护一个项名到整数的 Map,建树时只存整数,输出结果时再映射回字符串。第二,递归函数里尽量少用containers.Map,因为 MATLAB 的 Map 在深层递归里开销不小。可以用数组加二分查找替代。第三,如果数据量特别大,考虑把 FP-Tree 的节点存成并行数组(names、counts、parents、children分开存),减少结构体嵌套。
从那以后我每次跑关联规则之前,都会先用小样本(比如 100 条事务)跑一遍全流程,确认支持度、置信度、提升度的计算逻辑没问题,再上全量数据。这个习惯帮我省了很多次「跑了一小时发现结果全错」的后悔药。希望帮到你。
本文还有配套的精品资源,点击获取