news 2026/10/16 1:28:34

MATLAB实现FP-Growth关联规则挖掘:从原理到实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MATLAB实现FP-Growth关联规则挖掘:从原理到实战

简介:这份资源是面向数据挖掘初学者与MATLAB使用者的FP-Growth关联规则挖掘实现包,用于在交易数据中高效发现频繁项集并提取关联规则。包内共8个文件,以7个.m脚本和1个.mat数据集为主,脚本分别承担主流程调度、FP树构建、幂集与子集生成、规则展示及树结构可视化等职责,mydata数据集可直接用于演示算法运行,压缩包约5KB,轻量易读。资源完整呈现了从数据预处理、构造FP树、生成条件模式基到递归挖掘与规则提取的全过程,读者可借此理解支持度与置信度的计算逻辑,并在此基础上替换自有事务数据、调整最小支持度阈值,扩展到市场篮子分析、网络行为分析等场景。目前已有191人学习,适合希望动手复现经典算法、夯实关联规则挖掘基础的读者参考。

1. 从一份 MATLAB 关联规则挖掘包说起:它到底能干什么

如果你手头有一批事务型数据——比如超市购物篮、网站点击流、设备告警日志——想找出「买了 A 的人大概率也会买 B」这类规则,那这份 FP-Growth 关联规则挖掘的 MATLAB 实现包,就是拿来就能跑的工具。它把频繁项集挖掘和关联规则生成两件事都封装好了,输入一个事务矩阵或元胞数组,输出支持度、置信度、提升度齐全的规则表。适合谁?做数据挖掘课程设计的学生、需要快速验证关联规则效果的研究人员,以及不想从零手写 FP-Tree 的 MATLAB 工程师。相比 Apriori 反复扫库的笨办法,FP-Growth 只扫两遍数据,靠一棵压缩的前缀树完成挖掘,数据量稍大时差距非常明显。

2. 核心架构拆解:FP-Tree 构建与规则生成的两段式设计

2.1 整体流程与模块划分

这个包的结构不复杂,但分工很清楚。常见做法是拆成三个部分:数据预处理与格式转换、FP-Tree 构建与频繁项集挖掘、关联规则生成与指标计算。主入口脚本负责串联,用户只需要改数据加载那几行。

先看整体调用链。假设你拿到的是一个transactions元胞数组,每个元素是一条事务里的项集合:

% main.m 主流程示意 data = load('transactions.mat'); % 加载事务数据 minSup = 0.02; % 最小支持度阈值 minConf = 0.6; % 最小置信度阈值 % 第一步:扫描数据,统计项频次,过滤低频项 [itemFreq, filteredTrans] = preprocess(data.transactions, minSup); % 第二步:构建 FP-Tree fpTree = buildFPTree(filteredTrans, itemFreq); % 第三步:从 FP-Tree 挖掘频繁项集 freqItemsets = mineFreqItemsets(fpTree, minSup); % 第四步:由频繁项集生成关联规则 rules = generateRules(freqItemsets, minConf); % 第五步:输出规则表 disp(rules);

这段代码把整个流水线串起来了。preprocess负责第一遍扫描,统计每个项出现的次数,把低于最小支持度的项直接剔除——这一步很关键,能大幅缩小后续树的规模。buildFPTree是核心,把过滤后的事务逐条插入前缀树,相同前缀共享路径,节点上挂计数。mineFreqItemsets用递归的方式从树的叶节点往上挖条件模式基,构造条件 FP-Tree。generateRules则对每个频繁项集拆分子集,算置信度和提升度。

参数说明:minSup是支持度阈值,取值越小挖出的项集越多,但计算量也越大,一般从 0.01 到 0.1 之间试;minConf是置信度门槛,0.6 以上算比较强的规则。这两个参数没有万能值,得根据数据分布调。

2.2 FP-Tree 节点结构与插入逻辑

FP-Tree 的节点定义决定了整个挖掘效率。常见做法是用一个结构体数组或 MATLAB 的containers.Map来存节点,每个节点记录项名、计数、父节点索引、子节点列表,以及一个指向同项下一节点的链表指针(header table 用)。

% buildFPTree.m 核心片段 function fpTree = buildFPTree(transactions, itemFreq) % 按频次降序排列每个事务中的项 sortedTrans = cellfun(@(t) sortByFreq(t, itemFreq), ... transactions, 'UniformOutput', false); root = struct('item', [], 'count', 0, 'parent', 0, ... 'children', {{}}, 'next', 0); fpTree.nodes = root; fpTree.header = containers.Map('KeyType','char','ValueType','any'); for i = 1:length(sortedTrans) trans = sortedTrans{i}; currentNode = 1; % 从根节点开始 for j = 1:length(trans) item = trans{j}; childIdx = findChild(fpTree, currentNode, item); if isempty(childIdx) % 新建节点 newNode = struct('item', item, 'count', 1, ... 'parent', currentNode, ... 'children', {{}}, 'next', 0); fpTree.nodes(end+1) = newNode; childIdx = length(fpTree.nodes); fpTree.nodes(currentNode).children{end+1} = childIdx; updateHeader(fpTree, item, childIdx); else % 已有节点,计数加一 fpTree.nodes(childIdx).count = ... fpTree.nodes(childIdx).count + 1; end currentNode = childIdx; end end end

逻辑说明:sortByFreq把每条事务里的项按全局频次从高到低排,这样共享前缀的概率最大,树也最紧凑。插入时从根往下找,有对应子节点就计数加一,没有就新建。updateHeader维护 header table 的链表,方便后面按项快速找到所有节点。参数上,fpTree.nodes是节点数组,fpTree.header是项到节点链的映射。

这里有个容易翻车的点:MATLAB 里结构体数组动态增长很慢,数据量大时建议预分配节点数组,或者改用table类型。我一般会先估一个上限,用repmat预分配再裁剪。

2.3 条件模式基与递归挖掘

频繁项集挖掘是递归过程。从 header table 里频次最低的项开始,沿着链表往上找每个节点的路径,这些路径就是条件模式基。

% mineFreqItemsets.m 递归挖掘片段 function itemsets = mineFreqItemsets(fpTree, minSup) itemsets = {}; headerKeys = keys(fpTree.header); % 按项频次升序处理 [~, order] = sort(cellfun(@(k) getFreq(fpTree, k), headerKeys)); headerKeys = headerKeys(order); for i = 1:length(headerKeys) item = headerKeys{i}; % 收集条件模式基 paths = collectPaths(fpTree, item); % 构造条件 FP-Tree condTree = buildCondTree(paths, minSup); if ~isempty(condTree.nodes) && length(condTree.nodes) > 1 % 递归挖掘 subItemsets = mineFreqItemsets(condTree, minSup); % 合并当前项 for j = 1:length(subItemsets) itemsets{end+1} = [{item}, subItemsets{j}]; end end % 单项集本身 itemsets{end+1} = {item}; end end

collectPaths从 header table 拿到项的所有节点,沿父指针往上走到根,每条路径的计数取该节点的计数。buildCondTree用这些路径重新建一棵小树,再递归调用挖掘函数。递归终止条件是条件树只剩一条路径或为空。参数minSup在条件树构建时同样用来过滤低频项。

这段递归是 FP-Growth 的精髓,也是最容易出 bug 的地方。常见问题是路径收集时忘了把当前项本身排除,导致条件模式基里混入目标项,挖出的项集重复。排查时可以在collectPaths里打印路径长度和内容,对比预期。

3. 跑起来:数据准备、参数调优与结果解读

3.1 数据格式与加载

这个包对输入数据的要求不严,常见两种格式:元胞数组,每个元素是一条事务的项名列表;或者数值矩阵,每行是一条事务,列是项 ID,用 0 或 NaN 填充。如果你手上是 CSV,先转成元胞数组。

% loadData.m 从 CSV 加载并转换 function transactions = loadData(filename) raw = readcell(filename); % 读取为元胞 [m, n] = size(raw); transactions = cell(m, 1); for i = 1:m row = raw(i, :); % 去掉空值和 NaN valid = ~cellfun(@(x) isempty(x) || ... (isnumeric(x) && isnan(x)), row); transactions{i} = row(valid); end % 去掉空事务 transactions = transactions(~cellfun(@isempty, transactions)); end

逻辑说明:readcell保留原始类型,避免数值项被转成字符串后对不上。valid过滤空单元格和 NaN。最后去掉空事务,否则建树时会插入空路径。参数上,filename是 CSV 路径,要求每行一条事务,项之间用逗号分隔。

提示:如果项名里有逗号或引号,readcell可能解析错位,建议先用文本编辑器检查一遍,或者改用readtable指定分隔符。

3.2 参数调优的实操方法

minSup和minConf直接决定输出规则的数量和质量。我的习惯是先固定minConf = 0.5,把minSup从 0.1 往下调,观察频繁项集数量的变化曲线。当项集数量开始指数增长时,那个点就是合理下限。

% tuneParams.m 参数扫描 supRange = [0.1, 0.05, 0.02, 0.01, 0.005]; for s = supRange [~, filtered] = preprocess(transactions, s); tree = buildFPTree(filtered, getFreq(filtered)); itemsets = mineFreqItemsets(tree, s); fprintf('minSup=%.3f, 频繁项集数=%d\n', s, length(itemsets)); end

跑一遍就能看到拐点。如果minSup降到 0.005 时项集数从几百跳到几万,说明再低就没意义了,噪声规则会淹没真正有用的模式。minConf一般不低于 0.5,低于这个值的规则可信度太差,业务上没法用。

3.3 规则表字段与解读

生成的规则表常见字段有:前件、后件、支持度、置信度、提升度。支持度是前后件同时出现的概率,置信度是前件出现时后件出现的条件概率,提升度大于 1 说明正相关,等于 1 说明独立,小于 1 说明负相关。

字段含义典型取值
前件规则左侧项集{牛奶, 面包}
后件规则右侧项{黄油}
支持度前后件共现频率0.03
置信度前件→后件的条件概率0.72
提升度置信度与后件独立概率之比1.8

解读时优先看提升度大于 1.5 且支持度不太低的规则。支持度太低(比如 0.001)的规则虽然置信度高,但可能只是小样本巧合,业务上不可靠。我一般会按提升度降序排,再人工筛一遍。

4. 避坑与排查:那些跑不通和跑不对的情况

4.1 内存溢出:树节点爆炸

现象:数据量稍大(几万条事务)时 MATLAB 报Out of memory,或者卡在建树阶段不动。原因:结构体数组动态增长导致内存碎片,加上 FP-Tree 节点数随数据量线性增长,没有预分配。解决:建树前预估节点上限,用repmat预分配节点数组,插入时用计数器索引而不是end+1。另外可以把项名映射成整数 ID,减少字符串存储开销。

4.2 规则为空:阈值设太高

现象:程序跑完没报错,但规则表是空的。原因:minSup或minConf设得过高,没有项集能同时满足。解决:先把minSup降到 0.01 以下试跑,确认有频繁项集输出,再逐步往上调。如果频繁项集有但规则为空,说明minConf太高,降到 0.3 看看。

4.3 项集重复:条件模式基没排除自身

现象:输出里出现{A} -> {A}这种自反规则,或者同一项集被挖出多次。原因:collectPaths收集路径时把目标项本身也包含进去了,导致条件树里混入目标项。解决:在收集路径时跳过目标项节点,只取它到根路径上除自身外的部分。排查时打印条件模式基,确认没有目标项。

4.4 结果对不上:项排序不一致

现象:同样的数据跑两次结果不同,或者和别的实现对比对不上。原因:事务内项排序不稳定,MATLAB 的sort对相同频次的项顺序可能不同,导致树结构有差异。解决:排序时用项名做次级排序键,保证频次相同时顺序确定。sortByFreq里加一个tiebreak参数,按字母序排。

4.5 递归过深:栈溢出

现象:数据里存在很长的频繁项集(比如几十项),递归挖掘时 MATLAB 报递归深度超限。原因:mineFreqItemsets递归层数等于最长频繁项集的长度。解决:把递归改成显式栈的迭代实现,或者调大 MATLAB 的递归限制(不推荐,治标不治本)。更稳妥的做法是在预处理阶段限制单条事务的最大长度。

5. 二次开发:从能跑到好用,几个改造方向

5.1 加一个规则过滤与排序模块

原始输出是一堆规则,直接看很乱。我一般会在generateRules后面加一层过滤,按提升度和支持度双阈值筛,再按业务字段排序。

% filterRules.m 规则后处理 function filtered = filterRules(rules, minLift, minSup) keep = false(length(rules), 1); for i = 1:length(rules) keep(i) = rules(i).lift >= minLift && ... rules(i).support >= minSup; end filtered = rules(keep); % 按提升度降序 [~, idx] = sort([filtered.lift], 'descend'); filtered = filtered(idx); end

minLift建议设 1.2 以上,minSup根据数据量定。这样输出的规则表直接能用,不用再手动筛。

5.2 并行化挖掘

FP-Growth 的递归挖掘天然适合并行——每个项的条件树挖掘互不依赖。MATLAB 的parfor可以派上用场,把 header table 里的项分组,每组一个 worker 独立挖。

% parallelMine.m 并行挖掘片段 headerKeys = keys(fpTree.header); results = cell(length(headerKeys), 1); parfor i = 1:length(headerKeys) item = headerKeys{i}; paths = collectPaths(fpTree, item); condTree = buildCondTree(paths, minSup); if ~isempty(condTree.nodes) && length(condTree.nodes) > 1 subItemsets = mineFreqItemsets(condTree, minSup); results{i} = cellfun(@(s) [{item}, s], subItemsets, ... 'UniformOutput', false); else results{i} = {{item}}; end end itemsets = [results{:}];

注意parfor里不能直接改共享变量,每个 worker 的结果先存到独立 cell,最后合并。并行加速比取决于项数和 CPU 核数,项多时效果明显。

5.3 验证改造是否正确的土办法

改完代码最怕引入隐性 bug。我的习惯是留一个小规模基准数据集(比如 20 条事务、10 个项),手动算出所有频繁项集和规则,存成expected.mat。每次改完跑一遍对比函数:

% verify.m 结果对比 function ok = verify(newItemsets, expectedFile) load(expectedFile, 'expectedItemsets'); ok = isequal(sortCell(newItemsets), sortCell(expectedItemsets)); if ~ok disp('不一致,检查条件模式基和排序逻辑'); end end

sortCell把每个项集内部排序后再整体排序,消除顺序影响。这个土办法帮我抓过好几次排序不一致和路径收集的 bug。

从那以后我每次改挖掘逻辑,都强制先跑一遍这个小基准,确认输出和预期完全一致,再上大数据集。希望帮到你。

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

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

拉格朗日插值法详解:原理、代码实现与避坑指南

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

作者头像 李华
网站建设 2026/10/16 1:27:31

SO-ARM100 上手指南:3 步让开源机械臂从模型到仿真可视化

SO-ARM100 上手指南:3 步让开源机械臂从模型到仿真可视化 【免费下载链接】SO-ARM100 Standard Open Arm 100 项目地址: https://gitcode.com/GitHub_Trending/so/SO-ARM100 SO-ARM100 是一个开源低成本机械臂项目,包含 SO100 与 SO101 两代硬件设…

作者头像 李华
网站建设 2026/10/16 1:26:47

Spring Cloud Gateway的WebSocketRoutingFilter:原理、源码与踩坑全解析

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

作者头像 李华
网站建设 2026/10/16 1:26:47

电阻与电源等效变换全解析:从串并联化简到Y-Δ变换

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

作者头像 李华
网站建设 2026/10/16 1:26:38

前端定时器深度解析:从事件循环到实战避坑指南

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

作者头像 李华
网站建设 2026/10/16 1:26:35

源码编译与定制实战:从配置参数到二进制裁剪的完整指南

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

作者头像 李华