并行与分布式系统中的并行关联规则挖掘:并行化Apriori算法
今天我们来探讨并行与分布式环境下的一个经典数据挖掘算法——Apriori算法的并行化。Apriori算法用于从大规模交易数据集中挖掘频繁项集(即经常一起出现的商品组合),是关联规则学习的基础。在单机环境下,面对海量数据时,Apriori算法可能因需要多轮数据库扫描和候选项集爆炸而变得极其缓慢。因此,并行化成为提升其可扩展性的关键。
1. 问题描述与Apriori基础
关联规则挖掘问题:给定一个事务数据库(例如,超市的购物记录),目标是找出所有满足最小支持度(min_support)阈值的“频繁项集”,并从中生成满足最小置信度(min_confidence)的关联规则(如“买啤酒 → 买尿布”)。
Apriori算法的核心思想:
- Apriori原理:如果一个项集是频繁的,那么它的所有子集也都是频繁的。反之,如果一个项集是非频繁的,那么它的所有超集也都是非频繁的。这个原理用于裁剪候选项集,是算法高效的关键。
- 迭代过程:算法通过多轮迭代来发现所有频繁项集。第
k轮迭代的目的是找出所有频繁k-项集(即包含k个商品的频繁项集)。- 连接步:通过合并上一轮的频繁
(k-1)-项集来生成候选k-项集。 - 剪枝步:利用Apriori原理,检查候选
k-项集的所有(k-1)-子集是否都是频繁的,若不是则剪枝。 - 计数步:扫描整个事务数据库,统计每个候选
k-项集的支持度(即它在所有事务中出现的次数)。 - 筛选步:筛选出支持度不低于
min_support的候选k-项集,它们就是本轮找出的频繁k-项集。
- 连接步:通过合并上一轮的频繁
挑战:计数步是算法的主要瓶颈,因为它需要对整个数据库进行一遍或多遍全扫描。在大规模数据集上,这非常耗时。并行化的核心目标就是加速这个计数过程,同时保证结果的正确性。
2. 并行化策略:数据并行 vs. 任务并行
对Apriori进行并行化,主要有两种经典思路:
- 数据并行(Data Parallelism):将事务数据库水平划分(即按行/事务划分)成多个分片,分配给不同的处理器(或计算节点)。每个处理器独立地对其分配的事务分片进行“局部计数”,最后通过一个全局归约操作汇总所有处理器的局部计数,得到全局支持度。
- 任务并行(Task Parallelism):将候选项集集合垂直划分(即按项集划分)成多个子集,分配给不同的处理器。每个处理器负责计算分配给它的那一部分候选项集在整个数据库上的支持度。这通常需要每个处理器都能访问到整个事务数据库或其副本。
在实际应用中,数据并行因其更好的负载均衡和更少的通信开销而更为流行。下面我们将重点讲解基于数据并行的Apriori算法。
3. 基于数据并行的Apriori算法详述
假设我们有一个由 P 个处理器(或节点)组成的并行系统。事务数据库 D 被均匀地划分为 P 个互不相交的分片 D_1, D_2, ..., D_P,每个分片存储在一个处理器上。
算法过程(第 k 轮迭代):
步骤1:候选集生成与分发(全局操作)
- 这一步骤通常是集中式的或协作完成的。一个主节点(或者通过一个全局广播)执行连接步和剪枝步,基于上一轮发现的全局频繁
(k-1)-项集F_{k-1},生成本轮的候选k-项集C_k。 - 将完整的候选集
C_k广播给所有P个处理器。这一步确保了所有处理器都有一份相同的全局候选列表。
步骤2:局部计数(并行执行)
- 每个处理器
p_i独立地扫描自己本地存储的事务数据分片D_i。 - 对于
D_i中的每一条事务T,处理器p_i会检查事务T包含了C_k中的哪些候选k-项集。对于每一个被包含的候选c,处理器p_i在本地维护一个计数器local_count_pi[c],并将其值加1。 - 这个过程在
P个处理器上并行、异步地进行。每个处理器只处理本地数据,无需与其他处理器通信。
步骤3:全局归约与筛选(全局操作)
- 当所有处理器完成本地计数后,需要将所有
local_count_pi[c]汇总,以计算每个候选c在整个数据库D上的全局支持度。 - 全局归约(Global Reduction):这是一个关键的通信步骤。通常使用一个全局求和(All-Reduce-Sum) 操作。每个候选
c的全局支持度global_count[c]等于所有处理器上其本地计数之和:
global_count[c] = Σ_{i=1 to P} local_count_pi[c] - 支持度计算与筛选:得到
global_count[c]后,计算其支持度support(c) = global_count[c] / |D|(|D|是数据库总事务数)。 - 筛选出所有满足
support(c) >= min_support的候选c,它们构成了本轮的全局频繁k-项集F_k。
步骤4:迭代控制
- 如果本轮发现的
F_k不为空,并且k小于用户指定的最大项集大小,则用F_k替换F_{k-1},回到步骤1,开始下一轮(k+1)的迭代。 - 如果
F_k为空,则算法终止。
4. 关键优化与挑战
- 负载均衡:在数据并行中,通过均匀划分事务数据,可以自然地实现较好的负载均衡。但如果数据分布极度不均(例如某些分片中事务长度差异巨大),可能会导致计数阶段某些处理器负载过重。动态负载均衡策略(如工作窃取)可以缓解此问题。
- 通信开销:主要通信开销发生在两个地方:
- 候选集广播:每轮迭代需要广播候选集
C_k。当C_k规模巨大时,广播开销会很大。优化方法包括压缩候选集表示、使用高效的广播原语。 - 全局归约:将每个候选的
P个本地计数汇总。当候选集C_k规模很大时,这同样会产生巨大的通信量。可以采用组合的归约操作,或者对候选集进行划分,进行层次化归约。
- 候选集广播:每轮迭代需要广播候选集
- 候选集表示与计数优化:
- 哈希树(Hash Tree):一种常用的数据结构,用于高效存储候选集并加速“事务包含哪些候选”的查询过程。在并行版本中,每个处理器需要构建和维护自己的哈希树副本。
- 位图(Bitmap):将事务和项集表示为位向量,可以利用位运算(如 AND 操作)来快速检查包含关系,尤其适合硬件并行(如SIMD指令集)。
- 可扩展性:随着处理器数量
P的增加,步骤2(局部计数) 的时间会近乎线性减少(理想情况下)。然而,步骤1和步骤3 的通信和全局同步开销可能会成为新的瓶颈。因此,该并行算法的可扩展性受限于候选集C_k的规模。
5. 变体:Count Distribution 算法
上面描述的数据并行方案有一个更具体的名称,称为 “Count Distribution” 算法。它的特点是:每轮迭代,所有节点拥有相同的全局候选集副本,进行并行局部计数,最后通过一次全局通信(归约)汇总结果。这是Apriori最直观和常用的并行化模式。
总结一下并行Apriori的核心优势:
- 加速瓶颈:将最耗时的数据库扫描和计数任务分解到多个处理器上并行执行。
- 内存友好:每个节点只需要存储整个数据库的一部分,降低了单节点的内存压力。
- 简单有效:算法逻辑清晰,易于在MPI、MapReduce或Spark等并行编程模型上实现。事实上,Spark MLlib等框架中的FP-Growth并行实现也借鉴了类似的数据并行思想。
这个算法的核心思想是将巨大的、顺序的“数数”任务,变成了许多小规模的、可以同时进行的“数数”任务,最后再把大家数的结果加在一起,从而极大地提升了处理海量数据的能力。