关联规则挖掘3:Eclat算法——等价类转换(Equivalence Class Transformation)
目录
Eclat 算法思想
常见面试题与回答
1. Eclat 与 Apriori、FP-Growth 的区别?
2. Eclat 如何计算项集的支持度?
3. Eclat 的优缺点?
4. 如何优化 Eclat 的性能?
5. Eclat 的适用场景?
关键点总结
详细计算过程示例
Step 1: 构建垂直数据格式(倒排索引)
Step 2: 深度优先搜索(DFS)生成频繁项集
2.1 处理以 {A} 开头的项集
2.2 处理以 {B} 开头的项集
2.3 处理以 {C} 开头的项集
2.4 处理以 {E} 开头的项集
Step 3: 生成所有频繁项集
Step 4: 生成关联规则
关键点总结
Python代码
面试题
一、基础概念题
1. 请简要阐述 Eclat 算法的核心思想是什么?
2. Eclat 算法与 Apriori 算法最根本的区别是什么?
二、深入原理题
3. 请描述 Eclat 算法的基本步骤。
4. Eclat 算法的优点和缺点分别是什么?
三、对比与应用题
5. 在什么场景下,Eclat 算法会比 Apriori 和 FP-Growth 更适用?
6. 如何优化 Eclat 算法中 TID 列表的交集操作?
四、实战代码题(如果面试岗位偏重工程)
7. 如果让你实现 Eclat 算法,你会如何设计核心数据结构?
参考:Python数据挖掘实战:微课版 - 9.4 Eclat算法 - 王磊 邱江涛 - 微信读书
Eclat 算法思想
Eclat(Equivalence Class Clustering and bottom-up Lattice Traversal)是一种基于垂直数据格式的频繁项集挖掘算法,与 Apriori(水平格式)和 FP-Growth(前缀树)不同。其核心思想是:
-
倒排索引(垂直数据格式):
-
每个项(item)存储其出现的事务ID列表(TID-list),如:
A: [T1, T3] B: [T2, T3, T4] C: [T1, T2, T3]
-
通过交集运算计算项集的支持度(如
A ∩ C = [T1, T3]
,支持度=2)。
-
-
深度优先搜索(DFS):
-
从单一项开始,逐步扩展生成更大的频繁项集(如
A → A+B → A+B+C
)。 -
利用等价类(Equivalence Class)思想:共享相同前缀的项集可分组处理(如所有以
A
开头的项集)。
-
-
支持度计算优化:
-
通过TID-list的交集直接计算支持度,无需扫描原始数据集。
-
常见面试题与回答
1. Eclat 与 Apriori、FP-Growth 的区别?
-
数据格式:
-
Apriori/FP-Growth:水平格式(事务列表)。
-
Eclat:垂直格式(项→TID列表)。
-
-
搜索策略:
-
Apriori:广度优先(BFS),逐层生成候选项集。
-
FP-Growth:前缀树压缩+条件模式基。
-
Eclat:深度优先(DFS)+ 倒排索引。
-
-
优势:Eclat 适合稠密数据集(TID-list 交集计算快),Apriori 适合稀疏数据,FP-Growth 适合大数据集。
2. Eclat 如何计算项集的支持度?
-
答:通过 TID-list 的交集。例如
A: [1,3]
和B: [2,3]
的交集是[3]
,则{A,B}
的支持度为 1。
3. Eclat 的优缺点?
-
优点:
-
无需重复扫描数据库(依赖内存中的 TID-list)。
-
适合高支持度项集和稠密数据。
-
-
缺点:
-
TID-list 存储开销大(事务多时内存消耗高)。
-
不直接生成关联规则(需额外步骤)。
-
4. 如何优化 Eclat 的性能?
-
使用差分集(Diffset):存储 TID-list 的差集而非全集(减少内存)。
-
按支持度升序排序项:优先处理稀有项,减少后续交集计算量。
-
并行化:将项集分组并行处理。
5. Eclat 的适用场景?
-
数据集较小或中等规模(内存足够存储 TID-list)。
-
需要快速挖掘高支持度项集(如电商高频商品组合)。
-
不适用于超大规模数据(TID-list 内存爆炸)。
关键点总结
-
核心思想:垂直数据格式 + TID-list 交集 + 深度优先搜索。
-
面试亮点:对比 Apriori/FP-Growth,解释 TID-list 的优化原理。
-
实际应用:适合内存充足、需快速迭代的场景(如实时推荐系统)。
详细计算过程示例
我们以下面的数据集为例,逐步演示 Eclat 算法的执行流程:
事务数据集(TID: Transaction ID):
T1: A, C, D
T2: B, C, E
T3: A, B, C, E
T4: B, E
参数设置:
-
最小支持度(min_support)= 2
-
最小置信度(min_confidence)= 0.5
-
最小提升度(min_lift)= 1
Step 1: 构建垂直数据格式(倒排索引)
扫描数据集,记录每个项出现的 TID-list:
A: [T1, T3]
B: [T2, T3, T4]
C: [T1, T2, T3]
D: [T1]
E: [T2, T3, T4]
-
过滤非频繁项:
D
的支持度=1 < min_support,移除。 -
剩余频繁 1-项集:
{A}: [T1, T3], sup=2 {B}: [T2, T3, T4], sup=3 {C}: [T1, T2, T3], sup=3 {E}: [T2, T3, T4], sup=3
Step 2: 深度优先搜索(DFS)生成频繁项集
2.1 处理以 {A} 开头的项集
-
扩展 {A}:
-
与后续项
B
组合:{A,B}
的 TID-list =[T1,3] ∩ [T2,3,4] = [T3]
, sup=1 < 2 → 丢弃。 -
与
C
组合:{A,C}
的 TID-list =[T1,3] ∩ [T1,2,3] = [T1,3]
, sup=2 ≥ 2 → 保留。 -
与
E
组合:{A,E}
的 TID-list =[T1,3] ∩ [T2,3,4] = [T3]
, sup=1 < 2 → 丢弃。
-
-
扩展 {A,C}:
-
与后续项
B
组合:{A,B,C}
的 TID-list =[T1,3] ∩ [T2,3,4] ∩ [T1,2,3] = [T3]
, sup=1 < 2 → 丢弃。 -
与
E
组合:{A,C,E}
的 TID-list =[T1,3] ∩ [T2,3,4] ∩ [T2,3,4] = [T3]
, sup=1 < 2 → 丢弃。
-
-
结果:以
A
开头的频繁项集为{A}
,{A,C}
。
2.2 处理以 {B} 开头的项集
-
扩展 {B}:
-
与
C
组合:{B,C}
的 TID-list =[T2,3,4] ∩ [T1,2,3] = [T2,3]
, sup=2 ≥ 2 → 保留。 -
与
E
组合:{B,E}
的 TID-list =[T2,3,4] ∩ [T2,3,4] = [T2,3,4]
, sup=3 ≥ 2 → 保留。
-
-
扩展 {B,C}:
-
与
E
组合:{B,C,E}
的 TID-list =[T2,3] ∩ [T2,3,4] = [T2,3]
, sup=2 ≥ 2 → 保留。
-
-
扩展 {B,E}:
-
已无更高阶项集可扩展。
-
-
结果:以
B
开头的频繁项集为{B}
,{B,C}
,{B,E}
,{B,C,E}
。
2.3 处理以 {C} 开头的项集
-
扩展 {C}:
-
与
E
组合:{C,E}
的 TID-list =[T1,2,3] ∩ [T2,3,4] = [T2,3]
, sup=2 ≥ 2 → 保留。
-
-
扩展 {C,E}:
-
已无更高阶项集可扩展。
-
-
结果:以
C
开头的频繁项集为{C}
,{C,E}
。
2.4 处理以 {E} 开头的项集
-
无更高阶项集可扩展。
Step 3: 生成所有频繁项集
合并所有 DFS 路径的结果:
1-项集: {A}, {B}, {C}, {E}
2-项集: {A,C}, {B,C}, {B,E}, {C,E}
3-项集: {B,C,E}
Step 4: 生成关联规则
以 {B,C,E}
为例(sup=2):
-
规则 {B,C} → {E}:
-
置信度 =
sup({B,C,E}) / sup({B,C}) = 2/2 = 1.0
-
提升度 =
1.0 / (sup({E})/总事务数) = 1.0 / (3/4) ≈ 1.33
-
满足条件 → 保留。
-
-
规则 {B,E} → {C}:
-
置信度 =
2/3 ≈ 0.67
-
提升度 =
0.67 / (3/4) ≈ 0.89
-
提升度 < min_lift → 丢弃。
-
其他规则类似计算,最终输出:
A --> C, support=0.50, confidence=1.00, lift=1.33
C --> A, support=0.50, confidence=0.67, lift=1.33
B --> E, support=0.75, confidence=1.00, lift=1.33
E --> B, support=0.75, confidence=1.00, lift=1.33
B,C --> E, support=0.50, confidence=1.00, lift=1.33
B,E --> C, support=0.50, confidence=0.67, lift=0.89 (丢弃)
C,E --> B, support=0.50, confidence=1.00, lift=1.33
... (其他规则类似)
关键点总结
-
垂直格式优势:TID-list 直接支持快速交集运算,无需扫描原始数据。
-
DFS 路径:按
A→B→C→E
的顺序递归生成项集,避免冗余计算。 -
剪枝策略:若
{A,B}
非频繁,则所有包含{A,B}
的更高阶项集(如{A,B,C}
)均被剪枝。
通过逐步追踪 TID-list 的交集和 DFS 路径,可以清晰理解 Eclat 的执行逻辑!
Python代码
class Eclat: def __init__(self, min_support=3, min_confidence=0.6, min_lift=1):self.min_support = min_supportself.min_confidence = min_confidenceself.min_lift = min_liftdef invert(self, data):invert_data = {}fq_item = []sup = []for i in range(len(data)):for item in data[i]:if invert_data.get(item) is not None:invert_data[item].append(i)else:invert_data[item] = [i]for item in invert_data.keys():if len(invert_data[item]) >= self.min_support:fq_item.append([item])sup.append(invert_data[item])fq_item = list(map(frozenset, fq_item))return fq_item, supdef getIntersection(self, fq_item, sup):sub_fq_item = []sub_sup = []k = len(fq_item[0]) + 1for i in range(len(fq_item)):for j in range(i+1, len(fq_item)):L1 = list(fq_item[i])[:k-2]L2 = list(fq_item[j])[:k-2]if L1 == L2:intersection = list(set(sup[i]).intersection(set(sup[j])))if len(intersection) >= self.min_support:sub_fq_item.append(fq_item[i] | fq_item[j])sub_sup.append(intersection)return sub_fq_item, sub_supdef findFrequentItem(self, fq_item, sup, fq_set, sup_set):fq_set.append(fq_item)sup_set.append(sup)while len(fq_item) >= 2:fq_item, sup = self.getIntersection(fq_item, sup)if len(fq_item) > 0:fq_set.append(fq_item)sup_set.append(sup)else:breakdef generateRules(self, fq_set, rules, len_data):for fq_item in fq_set:if len(fq_item) > 1:self.getRules(fq_item, fq_item, fq_set, rules, len_data)def removeItem(self, current_item, item):tempSet = []for elem in current_item:if elem != item:tempSet.append(elem)tempFrozenSet = frozenset(tempSet)return tempFrozenSetdef getRules(self, fq_item, cur_item, fq_set, rules, len_data):for item in cur_item:subset = self.removeItem(cur_item, item)if subset in fq_set: # 确保子集在频繁项集中confidence = fq_set[fq_item] / fq_set[subset]supp = fq_set[fq_item] / len_datalift = confidence / (fq_set[frozenset([item])] / len_data)if confidence >= self.min_confidence and lift >= self.min_lift:flag = Falsefor rule in rules:if (rule[0] == subset) and (rule[1] == fq_item - subset):flag = Trueif not flag:rules.append((subset, fq_item - subset, supp, confidence, lift))if len(subset) >= 2:self.getRules(fq_item, subset, fq_set, rules, len_data)def fit(self, data, display=True):frequent_item, support = self.invert(data)frequent_set = []support_set = []len_data = len(data)self.findFrequentItem(frequent_item, support, frequent_set, support_set)data_dict = {}for i in range(len(frequent_set)):for j in range(len(frequent_set[i])):data_dict[frequent_set[i][j]] = len(support_set[i][j])rules = []self.generateRules(data_dict, rules, len_data)if display: print("Association Rules:")for rule in rules:print(f"{list(rule[0])} --> {list(rule[1])}, support={rule[2]:.3f}, confidence={rule[3]:.3f}, lift={rule[4]:.3f}")print("发现的规则数量:", len(rules))return frequent_set, rules# 测试数据
itemSetList = [['A', 'C', 'D'],['B', 'C', 'E'],['A', 'B', 'C', 'E'],['B', 'E']]et = Eclat(min_support=2, min_confidence=0.5, min_lift=1)
et.fit(itemSetList, True)
面试题
一、基础概念题
1. 请简要阐述 Eclat 算法的核心思想是什么?
答案:Eclat 算法的核心思想是基于垂直数据格式和使用交集运算来支持度。
-
垂直数据格式:它不像 Apriori 那样记录每个事务包含了哪些项,而是记录每个项出现在哪些事务中(即事务ID列表,简称TID列表)。例如,项
A
的 TID 列表是{1, 3, 4}
。 -
交集求支持度:要计算一个项集
{A, B}
的支持度,不需要扫描整个数据库,只需对项A
的 TID 列表和项B
的 TID 列表求交集。交集的大小就是这个项集的支持度计数。例如,TID(A) ∩ TID(B) = {1, 3}
,则support({A,B}) = 2
。 -
深度优先搜索:Eclat 通常采用深度优先的策略来遍历项集空间,这有助于减少内存中对中间结果的存储开销。
2. Eclat 算法与 Apriori 算法最根本的区别是什么?
答案:最根本的区别在于它们使用的数据格式和支持度计算方式。
-
数据格式:Apriori 使用水平数据格式(Horizontal Format),即
事务ID -> {物品列表}
。而 Eclat 使用垂直数据格式(Vertical Format),即物品 -> {事务ID列表}
。 -
支持度计算:Apriori 在生成候选集后,需要通过扫描整个数据库来统计每个候选集的支持度。这是一个非常耗时的过程。Eclat 则通过简单的集合交集操作来计算支持度,避免了反复扫描原始数据库,效率更高。
二、深入原理题
3. 请描述 Eclat 算法的基本步骤。
答案:
-
数据转换:首先将水平格式的数据库转换为垂直格式,为每个1项集(单个项)建立其 TID 列表。
-
剪枝:根据最小支持度阈值,找出所有频繁1项集
(L1)
。 -
递归扩展(深度优先搜索):对于每一个频繁k项集
X
,将其与排在它后面的其他频繁k项集Y
进行如下操作:-
连接:生成候选
k+1
项集Z = X ∪ Y
。 -
计算支持度:通过计算
X
和Y
的 TID 列表的交集,得到Z
的 TID 列表。交集的大小即为Z
的支持度计数。 -
剪枝:如果
Z
的支持度大于等于最小支持度,则将其加入到频繁k+1
项集列表中。
-
-
重复:以新生成的频繁项集为起点,递归地重复步骤3,直到无法生成新的频繁项集为止。
4. Eclat 算法的优点和缺点分别是什么?
答案:
-
优点:
-
高效的支持度计算:利用集合交集操作,避免了多次扫描原始数据库,尤其在支持度阈值较低、数据集密度较高时表现优异。
-
深度优先搜索:内存占用相对较小,因为它不需要同时保存大量的候选集(与 Apriori 的广度优先相比)。
-
无需候选集生成:在递归过程中直接通过交集产生下一个项集,避免了 Apriori 中复杂的候选生成步骤。
-
-
缺点:
-
TID 列表开销:如果项集很长或数据库非常庞大,TID 列表本身会消耗大量内存。对于超大规模数据集,这可能成为瓶颈。
-
交集操作成本:当两个很长的 TID 列表进行交集运算时,虽然比扫描数据库快,但本身也是一个计算密集型操作。
-
对稀疏数据集不友好:在非常稀疏的数据集中,TID 列表会很短,但其优势可能不如 FP-Growth 等算法明显。
-
三、对比与应用题
5. 在什么场景下,Eclat 算法会比 Apriori 和 FP-Growth 更适用?
答案:
Eclat 算法在以下场景中可能更具优势:
-
内存充足但数据库庞大:当数据库太大,反复扫描(如 Apriori)的成本极高时,Eclat 的一次转换、多次交集的方式会更高效。
-
密集型数据集:数据集中存在大量频繁模式时,Eclat 的垂直格式和交集操作能充分发挥其优势。
-
支持度阈值设置较低:低支持度意味着会挖掘出更长的频繁项集,Eclat 的深度优先搜索和基于交集的支持度计算在这种场景下扩展性更好。
对比总结:
-
vs Apriori:在绝大多数情况下,Eclat 都优于 Apriori,因为它解决了 Apriori 候选集生成和多次扫描数据库两大瓶颈。
-
vs FP-Growth:这是一个更有意义的对比。FP-Growth 通过构建紧凑的 FP-tree 来压缩数据库,通常性能最佳且更稳定。但在某些特定类型的数据集(如非常稠密的数据)上,Eclat 可能能与之一战。FP-Growth 通常是更通用的首选。
6. 如何优化 Eclat 算法中 TID 列表的交集操作?
答案:
有多种优化策略可以显著提升交集操作的效率:
-
使用位图(Bitmap):将 TID 列表用位图来表示。每个位代表一个事务,如果项在该事务中出现,则对应位设置为1。求交集就变成了非常快速的按位与(BITWISE-AND) 操作,这在现代计算机上效率极高。
-
使用差分编码:如果 TID 是连续的数字,可以存储事务ID的差值而不是原始值,这样可以减少存储空间。
-
列表排序与二分查找:保证所有 TID 列表是有序的。在求两个有序列表的交集时,可以使用双指针法在线性时间内完成,效率很高。
-
倒排索引:这本身就是 Eclat 的基础,但可以进一步优化索引结构。
四、实战代码题(如果面试岗位偏重工程)
7. 如果让你实现 Eclat 算法,你会如何设计核心数据结构?
答案:我会设计一个以项集为键(Key),以其对应的 TID 列表为值(Value)的映射(Map)或字典(Dictionary)作为核心数据结构。
-
初始状态:
Map<String, List<Integer>>
,键是单个项(如 "A"),值是这个项出现的所有事务ID的列表[1, 2, 5]
。 -
递归过程:在生成
k+1
项集时,新的键是k
项集的字符串连接(如 "AB"),新的值是两个父k
项集 TID 列表的交集结果。
伪代码思路:
def eclat(prefix, items, min_sup):while items:i, tid_list_i = items.pop(0)new_prefix = prefix + i # 例如 prefix="A", i="B" -> new_prefix="AB"# 记录频繁项集frequent_sets.append((new_prefix, len(tid_list_i))) new_items = []for j, tid_list_j in items:# 关键操作:求交集intersection = tid_list_i ∩ tid_list_j if len(intersection) >= min_sup:new_items.append( (j, intersection) )# 深度优先递归eclat(new_prefix, new_items, min_sup)