并行与分布式系统中的并行关联规则挖掘:并行化FP-Growth算法
题目描述
关联规则挖掘是数据挖掘中的经典任务,用于发现事务数据库中项集之间的有趣关系(例如,“购买啤酒的人通常也会购买尿布”)。Apriori算法是其代表性算法,但需要多次扫描数据库并产生大量候选项集,效率较低。FP-Growth(Frequent Pattern Growth)算法通过构建一棵称为FP树(Frequent Pattern Tree)的紧凑数据结构,避免了候选项集的生成,大幅提升了效率。在并行与分布式环境中,如何将FP-Growth算法高效地并行化,以处理海量数据集,是本题目要解决的核心问题。
问题定义:给定一个由多个事务(每个事务是若干项的集合)组成的大型数据集、一个最小支持度阈值min_sup,要求并行地挖掘出所有满足支持度不低于min_sup的频繁项集,并支持从中生成关联规则。
解题过程循序渐进讲解
步骤1:理解串行FP-Growth算法
在讲解并行化之前,必须先理解串行FP-Growth的工作原理,这是并行化的基础。
- 第一次扫描数据库:统计每个单项(1-项集)的出现频率。丢弃那些支持度低于min_sup的项,将剩余的频繁项按频率降序排序,得到一个频繁项列表(记为
L)。 - 第二次扫描数据库:为每个事务进行处理。
- 按照
L的顺序,过滤掉事务中的非频繁项。 - 将过滤后并排序的项集插入到FP树中。FP树是一种前缀树(Trie),根节点为“null”。插入时,从根节点开始,按照项的顺序创建或更新路径上的节点。每个节点维护一个计数值(count),记录共享此前缀路径的事务数。同时,维护一个项头表(Header Table),将相同项的所有节点通过链表(node-links)连接起来,便于快速访问。
- 按照
- 递归挖掘FP树:
- 基本思想:对于FP树中的每个频繁项(通常从频率最低的开始,即
L的倒序),以其作为“后缀”,挖掘包含它的所有频繁项集。 - 方法:
a. 从项头表中找到项α的所有节点,沿着每个节点的父指针(parent)向上遍历,直到根节点,收集到的路径称为α的条件模式基(Conditional Pattern Base)。每条路径的计数由α节点的计数决定。
b. 用这些条件模式基构建一棵新的树,称为α的条件FP树。构建过程与主树类似,但只使用本地计数。
c. 如果条件FP树非空(通常是一条单一路径或一棵树),则递归地挖掘这棵树,得到的所有频繁项集后缀上α,即为包含α的频繁项集。
d. 如果条件FP树是一条单一路径,则可以直接枚举该路径上所有节点组合(幂集)与α的组合,生成所有频繁项集。
- 基本思想:对于FP树中的每个频繁项(通常从频率最低的开始,即
关键点:FP-Growth通过共享前缀压缩了数据表示,并将挖掘任务转化为递归地构建和挖掘更小的条件FP树。
步骤2:并行化挑战与策略分析
串行FP-Growth的瓶颈主要在于:
- 两次串行数据库扫描。
- 单线程递归挖掘FP树(特别是当条件FP树很大时)。
并行化策略通常采用数据并行和任务并行相结合的方式:
- 数据并行:将原始事务数据集划分到多个处理器(或机器)上。
- 任务并行:将频繁项集的挖掘任务(对应不同的后缀项)分配给不同的处理器。
一个经典的并行化框架是基于数据库划分的并行FP-Growth(例如,在MapReduce框架下的PFP)。
步骤3:基于MapReduce的并行FP-Growth算法详解
这里我们以广泛应用的MapReduce实现为例,详细拆解其并行步骤。整个过程通常分为多个MapReduce作业(Jobs)。
阶段一:计算全局频繁1-项集
- 目标:生成全局的频繁项列表
L。 - 过程:
- Map阶段:每个Mapper处理一个数据分片(一部分事务)。对于事务中的每个项,输出键值对
(item, 1)。 - Reduce阶段:Reducer接收同一个
item的所有计数值(1),进行求和,得到该项的全局支持度。过滤掉支持度< min_sup的项,将剩余的频繁项按支持度降序排序,输出全局列表L,并广播给所有节点。
- Map阶段:每个Mapper处理一个数据分片(一部分事务)。对于事务中的每个项,输出键值对
阶段二:构建分组的事务数据(或直接构建FP树)
- 目标:将事务数据按一定规则重新组织,为并行挖掘做准备。
- 核心思想:为了避免所有节点都构建完整的全局FP树(通信和内存开销大),我们将
L中的频繁项分成若干组(例如,G组)。每个组负责挖掘以其组内项为后缀的所有频繁项集。 - 过程 - Map阶段(事务划分):
- 每个Mapper读入一个事务和全局列表
L。 - 过滤并排序事务中的项(同串行步骤)。
- 对于排序后事务中的每一项,我们将其分配给一个特定的组。常见的分组策略是:项
x属于哪个组,由L中排在x之后的第一项(称为x的后继)所在的组决定,或者更简单地,将L均匀分为G段,每段对应一个组,项x属于包含其后继项的那个组。对于事务中最后一项,可以分配给一个特殊的组。这样,一个事务可能被复制到多个组中(因为它包含多个项),但每个事务在每个组中只会保留与改组相关的后缀部分。 - Mapper输出键值对:
(group_id, 过滤排序后的事务或其子集)。
- 每个Mapper读入一个事务和全局列表
- 过程 - Reduce阶段(组内数据聚合):
- Reducer接收同一个
group_id下的所有(部分)事务。这些事务共享相似的后缀模式。 - 在Reducer内部(或随后的本地处理),可以为这个组的数据独立地构建一棵局部的FP树。这棵树是全局FP树的一个投影(Projection),只包含与这个组相关的项和事务。
- Reducer接收同一个
阶段三:并行挖掘频繁项集
- 目标:在每个组内,并行地挖掘以该组项为后缀的频繁项集。
- 过程:
- 任务划分:对于一个组,其局部的频繁项列表(是
L的子集)可以进一步划分,或者直接对组内的局部FP树进行递归挖掘。由于组已经将数据规模减小,且组间挖掘任务完全独立,因此可以自然地并行。 - 并行执行:每个组(或组内进一步划分的任务)被分配到一个计算节点。节点加载其对应的局部FP树(或事务数据),运行串行FP-Growth算法的挖掘部分(即递归构建条件FP树),挖掘出所有以该组项为后缀的频繁项集。
- 结果输出:每个并行任务输出它找到的频繁项集及其支持度。注意,这里的支持度是基于该组局部数据计算出的局部支持度,但它等于全局支持度吗?是的。因为阶段二的划分方法保证了:任何包含特定后缀
α的事务,其投影都会被且仅会被发送到负责挖掘α的那个组中。因此,在负责α的组内计算出的包含α的项集的支持度,就是其全局支持度。
- 任务划分:对于一个组,其局部的频繁项列表(是
阶段四(可选):聚合结果与生成关联规则
- 将阶段三所有并行任务输出的频繁项集合并,去重(如果需要),即得到全部的全局频繁项集。
- 关联规则的生成(计算置信度等)可以作为一个单独的并行任务,对挖掘出的频繁项集进行处理,由于其计算相对独立,也易于并行化。
步骤4:算法特点与优化
- 负载均衡:分组策略(
L的划分)直接影响负载均衡。若项的频率分布高度倾斜(如少数项极其频繁),可能导致某些组(负责高频项)的任务过重。可以采用更智能的、基于频率的分组策略,或将高频项单独处理。 - 通信与存储优化:阶段二中,一个事务可能被复制到多个组,增加了网络通信和存储开销。这是用空间换并行度的典型折衷。可以通过更精细的划分或压缩技术来缓解。
- 容错性:在MapReduce等框架下,每个阶段的任务失败都可以自动重新调度,具有很好的容错性。
- 扩展性:算法可以很好地扩展到数百甚至数千个节点,适用于大规模数据集。
总结
并行化FP-Growth算法的核心思想是 “分而治之”+“投影” 。通过将全局的频繁项列表划分为组,巧妙地将原始的、庞大的挖掘任务,分解为大量独立的、在小规模数据(局部FP树)上进行的子任务。这些子任务之间几乎没有依赖,可以高度并行化。基于MapReduce的实现清晰地展示了如何将数据分布、任务分发、局部计算和结果聚合等步骤,映射到并行计算框架的抽象上,是并行与分布式算法设计的一个优秀范例。