数据挖掘之Apriori频繁项集挖掘
本文的代码文件原件可以在我们的 "数据臭皮匠" 中输入"第六章1" 拿到
1.基本概念介绍
在深入学习该领域之前,请先掌握一些基础概念,在深入学习该领域之前,请先掌握一些基础概念,在深入学习该领域之前,请先掌握一些基础概念,在深入学习该领域之前,请先掌握一些基础概念,在深入学习该领域之前,请先掌握一些基础概念
首先假设我们有一个由多行多列组成的表格
事务: 可以将事务简单理解为表格的其中一行
事务集: 一个表格会有多行, 这个包括多个事务的集合就叫事务集
项: 表格中每一行会包括多个值, 每一个值就是一个项
项集: 多个项组成一个集合, 这个由多个项组成的集合就叫项集
k项集: 包含k个项的项集为k项集 , 如{I1,I2} 为一个2项集
支持度: 给定一个A项集, 整个表格共有N行数据, 其中包含该A项集的记录有m行, 则其绝对支持度即为m, 而其相对支持度则计算为该比例.
在表格中, 含有项集A的记录共有n1条, 同时含有项集A与B的记录有n2条, 置信度c定义为两者的比例即c = n2/n1
频度: 表格中有n1行包括项集A, n1即为A的频度
频繁项集: 当某项集相对于其全集的支持度低于设定的标准阈值时, 则称该项集为频繁项集
规则: 假设有A,B两个项集, 由A出现可以推出B也出现(即A=>B) 这就是一条规则
强规则: 同时满足最小支持度阈值和最小置信度阈值的规则为强规则
2.闭频繁项集和极大频繁项集定义
在处理大型数据集时挖掘频繁项集的主要困难在于:这种挖掘往往会产生大量满足最低支持度标准的支持项目组合(因为一个高频项目集合的所有子项目组合都是同样具有频率特征)。具体来说,在包含1₀₀个项目的大规模数据集中识别出一个完整的项目集合{a₁,a₂,…,a₁₀₀}时(即该集合在N行记录中出现至少m次),这个过程将导致该集合的所有可能子项目组合也被识别为高频项目组合(共有1₀₀个项目组合)。

=4950个频繁2项集{a1,a2}, {a1,a3}, ...{a99,a100} , 因此, 该100项的频繁项集共可以产生1.23*

个频繁项集, 对于任何计算机, 这个项集都太大了, 大到无法计算和存储。
真超集:设X,Y为两个集合,则若集合X是集合Y的真子集(即每个元素都在集合Y中存在),则称集合Y为集合X的真超集。(其中每个元素都在集合Y中存在)而集合Y至少包含一个不在集合X中的元素。
闭的项集: 称其为闭当且仅当对于X项集而言, 表格中不存在与之具有等价的支持度并为其真超标的集合。
闭频繁项集:如果项集X是闭的且频繁的, 则项集X为闭频繁项集。
极大频繁项集:当一个频繁项集X的所有可能的超项都不是高频项集时,则称其为极大频繁项集
3.Apriori(先验)算法
该算法采用层次遍历法进行迭代运算。随后系统会遍历数据库以统计每个项目出现的次数,并汇总所有频繁的一个项目集合。将这些项目组合记为候选项集 L₁。接着利用这些候选项集找出所有频繁的二项目组合,并将结果存入候选项集 L₂ 中。继续这一过程并生成新的候选项集 L₃,...,直至无法再发现新的满足条件的 k-项目组合为止。
为了优化频繁项集逐层生成的效率时 一种被称为先验属性的关键属性被用来缩减搜索范围
先验性质: 频繁项集的所有非空子集也一定是频繁的,从

到
需要两步:连接步 和剪枝步
3.1 连接步
为找出

,通过将

与自身连接产生候选k项集的集合, 该候选k项集的集合记为

,假设

和

是

中的项集,
表示项集

的第j 项。首先将

中的所有项集

排序,使得
<
<...<
, 然后执行连接



即

中的项集互相连接, 如果

和

的前k-2项都是相同的, 则两者是可以连接的,连接的结果为 

该连接过程需要将

中的元素两两连接, 执行完一遍
3.2 剪枝步
由连接步得到

后, 可以扫描数据库, 计算其中每个候选k项集的计数, 从而确定

,但

规模较大的一组数据,在进行遍历时可提前利用其先验特性来缩减候选集中k项集的数量。这一过程被称为剪枝。其背后的逻辑是:若该k项集中任一其包含的k-1项目子集未被包含于已知的频繁(k−1)项目集中

中,则该k项集一定不是频繁项集,从而可以从

中删除。

书中P162-P163段落详尽阐述了一个具体过程,读者应好好研读一下,并大致能较好地掌握这一流程。
4.Apriori算法的代码实现
本代码基于书中提供的数据集,在Apriori算法的基础上进行了逐步实现,并对上文中使用的文字进行了详细对照。通过运行代码后可以看出生成的结果与书中保持一致。
def aprioriGen(Lk_1, k): #creates Ck
""" 根据k-1项集产生候选k项集
Lk_1: k-1项集的列表
k: k项集的k
"""
retList = []
lenLk_1 = len(Lk_1) # k-1项集的长度
for i in range(lenLk_1):
for j in range(i+1, lenLk_1):
L1 = sorted(list(Lk_1[i])) # k等于2时, 1项集取空, k等于3时,2项集取第一个元素, k等于4时,3项集取前两个元素
L2 = sorted(list(Lk_1[j])) # k等于2时, 1项集取空, k等于3时,2项集取第一个元素, k等于4时,3项集取前两个元素
if L1[:k-2]==L2[:k-2]: # 如果前k减2个元素相等 , 就将两几何求并
retList.append(Lk_1[i] | Lk_1[j]) #set union
return retList
def get_subset(ss):
"""求一个k项集的所有k-1项集子集"""
ls = list(ss)
res = []
for i in range(len(ls)):
res.append(set(ls[:i] + ls[(i+1):]))
return res
def check_in(Lk_1,ck):
""" 返回布尔值,
检验候选k项集的所有k-1项集子集 是否都在L_(k-1)中
"""
i = 0
# 取出候选k项集的一个k-1项集子集
for ss_sub in get_subset(ck):
# 如果该k-1项集子集在在L_(k-1)中, 加1
if ss_sub in Lk_1:
i+= 1
# 返回候选k项集的所有k-1项集子集 是否都在L_(k-1)中
return i == len(ck)
def cut_branch(Ck,Lk_1):
"""剪枝,
只保留候选K项集集合中, 所有k-1项集都在L_(k-1) 的候选k项集
"""
Ck_res = []
# 取出一个候选k项集
for ck in Ck:
# 判断候选k项集的所有k-1项集子集 是否都在L_(k-1)中
flag = check_in(Lk_1,ck)
# 如果是, 保留该候选k项集
if flag :
Ck_res.append(ck)
return Ck_res
def createC1(dataSet):
"""从数据集中构建一项集候选集,
将每个一项集都以frozenset的数据类型保存
因为frozenset可以作为字典的key, 常规的set不可以, 方便后续对候选项集计数
"""
C1 = []
for transaction in dataSet:
for item in transaction:
if not [item] in C1:
C1.append([item])
# frozenset 可以作为字典的key, set 不可以
return [frozenset(i) for i in C1]
def scanD(D, Ck, minSupport):
"""
D:数据集
Ck:候选k项集
minSupport:最小支持度阈值
"""
# 扫描数据集,确定Ck中每个候选的计数
ssCnt = {}
for tid in D:
for can in Ck:
if can.issubset(tid):
ssCnt[can] = ssCnt.get(can,0)+1
# 根据最小支持度阈值筛选出频繁k项集
retList = []
supportData = {}
for key in ssCnt:
# 计算备选k项集的支持度
support = ssCnt[key]
# 如果支持度大于阈值, insert进k项集的结果列表
if support >= minSupport:
retList.insert(0,key)
# 不管支持度是否大于阈值, 都记录下该备选k项集的支持度
supportData[key] = support
return retList, supportData
def apriori(dataSet, minSupport):
C1 = createC1(dataSet)
# print('C1:',C1)
D = [set(i) for i in dataSet]
# 检查C1中每个备选1项集的支持度, L1为筛选出的1项集, supportData为每个备选1项集的支持度
L1, supportData = scanD(D, C1, minSupport)
print(f'L1:{L1}','\n')
L = [L1] # 将1项集列表插入频繁k项集的结果列表
k = 2 # 2项集
# k项集为空的时候停止
while (len(L[k-2]) > 0):
Ck = aprioriGen(L[k-2], k) # 连接步,产生备选k项集
print('过滤前:',len(Ck),Ck,'\n')
Ck = cut_branch(Ck,L[k-2]) # 剪枝
print('过滤后:',len(Ck),Ck,'\n')
Lk, supK = scanD(D, Ck, minSupport)#scan DB to get Lk 扫描数据集,确认C_k中的每个候选k项集是否为频繁项集
print('筛选后:',len(Lk),Lk,'\n')
supportData.update(supK)
L.append(Lk)
k += 1
return L, supportData
data_ls =[['I1','I2','I5'],
['I2','I4'],
['I2','I3'],
['I1','I2','I4'],
['I1','I3'],
['I2','I3'],
['I1','I3'],
['I1','I2','I3','I5'],
['I1','I2','I3']]
L, supportData =apriori(data_ls,minSupport = 2)
print('L:',L,'\n')
print("supportData:",supportData)
结果为

参考文档:
数据挖掘概念与技术(原书第三版)
机器学习实战(人民邮电出版社) 第11章
关注公众号:数据臭皮匠;获得更多精彩内容
作者:范小匠
审核:灰灰匠
编辑:森匠
