Apriori算法是一种挖掘关联规则的频繁项集算法,FP-growth,又称为FP-增长算法,它比Apriori算法要快,它基于Apriori构建。关联分析在数据挖掘当中有非常经典的成功案例,著名的啤酒尿布就来自于关联算法的贡献。关联分析体现在两个方面:
1.频繁项集(frequency item sets):经常同时出现的一些元素的集合。
2.关联规则(association rules): 意味着两种元素之间存在很强的关系。
交易号码 | 商品 |
0 | 豆奶, 甘蓝 |
1 | 甘蓝,尿布,葡萄酒,甜菜 |
2 | 甘蓝,尿布,葡萄酒,橙汁 |
3 | 甘蓝,豆奶,尿布,葡萄酒 |
4 | 甘蓝,豆奶,尿布,橙汁 |
如上所示的购物清单列表,{尿布,葡萄酒}更为频繁的出现在每条清单中,所以{尿布,葡萄酒}就是一个频繁项集。同时也可以得到一些关联规则例如:尿布—>葡萄酒,意味着买尿布的人同时也有较大的可能性买葡萄酒。那么究竟该如何定义频繁?关联?,这里引入两个重要概念:
(一)支持度:一个项集的支持度(support)定义为数据集中包含该项集的记录占总记录的比例。表中可以看出项集 {豆奶} 的支持度为3/5;{豆奶,尿布} 的支持度为2/5
(二)可信度置信度(confidence):是专门为尿布—>葡萄酒这样的关联规则定义的,这条规则的可信度定义为“ 支持度({尿布,葡萄酒}) / 支持度({尿布})”。表中可以发现 {尿布,葡萄酒} 的支持度是3/5,{尿布} 的支持度为4/5, 所以关联规则 “尿布—>葡萄酒”的可信度为3/4=0.75,也就是说对于所有包含 "尿布"的记录中,该关联规则对其中的 75% 记录都适用。
Apriori原理:假设这里有商品0,1,2,3 可以通过以下的方式获得商品的不同组合
我们的目标是找到经常在一起购买的物品集合。这里使用集合的支持度来度量其出现的频率。一个集合出现的支持度是指有多少比例的交易记录包含该集合。而使用Apriori算法可以降低计算量,减少遍历数据的频次。Apriori原理:一个频繁项集它所有的子集也是频繁的,即如果 {0,1} 是频繁的,那么 {0}, {1} 也一定是频繁的。同时反过来讲,如果一个项集是非频繁的,那么它所有的超集也是非频繁的。这样就省下了部分项集超集的计算。
算法过程描述:
首先会生成所有单个物品的项集列表;
扫描交易记录来查看哪些项集满足最小支持度要求,去掉不满足最小支持度的集合;
对剩下的集合进行组合以生成包含两个元素的项集;
接下来重新扫描交易记录,去掉不满足最小支持度的项集,重复进行直到所有项集都被去掉。
def loadDataSet():
return [ [ 1, 3, 4 ], [ 2, 3, 5 ], [ 1, 2, 3, 5 ], [ 2, 5 ] ]
def createC1( dataSet ): #C1是大小为1的所有候选项集的集合
C1 = []
for transaction in dataSet:
for item in transaction:
if [ item ] not in C1:
C1.append( [ item ] )
C1.sort()
return list(map( frozenset, C1 ))
#计算Ck中的项集在数据集合D中的支持度
#返回满足最小支持度的项集的集合,和所有项集支持度信息的字典
def scanD( D, Ck, minSupport ):
ssCnt = {}
for tid in D:
#对于每一条transaction
for can in Ck:
if can.issubset( tid ):#对于每一个候选项集can,检查是否是transaction的一部分,即该候选can是否得到transaction的支持
ssCnt[ can ] = ssCnt.get( can, 0) + 1 #当键不存在时赋值为0
numItems = float(len(D))
retList = []
supportData = {}
for key in ssCnt:
#每个项集的支持度
support = ssCnt[ key ] / numItems
#将满足最小支持度的项集,加入retList
if support >= minSupport:
retList.insert( 0, key )
#汇总支持度数据
supportData[ key ] = support
return retList, supportData#返回所有的频繁项集和支持度信息
if __name__ == '__main__':
myDat = loadDataSet()
C1 = createC1( myDat )
#构建集合表示的数据集D
D = list(map( set, myDat ))
L, suppData = scanD( D, C1, 0.5 ) #注意:map类型的D,C1都在迭代一次之后变为空!
print("频繁项集L:", L)
print("所有候选项集的支持度信息:", suppData)
频繁项集L: [frozenset({5}), frozenset({2}), frozenset({3}), frozenset({1})]
所有候选项集的支持度信息: {frozenset({1}): 0.5, frozenset({3}): 0.75, frozenset({4}): 0.25, frozenset({2}): 0.75, frozenset({5}): 0.75}
上面我们只是选出了项集中只包含一个元素的频繁项集,下面整合上面的代码,选出包含 2个,3个直至包含所有候选元素的频繁项集。
def aprioriGen( Lk, k ):
#由初始候选项集的集合L1生成新的候选项集,
#k表示生成的新项集中包含的元素个数
retList = []
lenLk = len( Lk )
for i in range( lenLk ):
for j in range( i + 1, lenLk ):
L1 = list( Lk[ i ] )[ : k - 2 ]#aprioriGen(L1,2)的情况下,L1=L2=[],aprioriGen(L2,3)情况下,第一个元素相等时才合并
L2 = list( Lk[ j ] )[ : k - 2 ]
L1.sort();L2.sort()
if L1 == L2:
retList.append( Lk[ i ] | Lk[ j ] )
return retList
#L1=[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})]
#L2=[frozenset({1, 3}),frozenset({1, 2}),frozenset({1, 5}),frozenset({2, 3}),frozenset({3, 5}),frozenset({2, 5})]
#aprioriGen(L2,3)
def apriori( dataSet, minSupport = 0.5 ):
C1 = createC1( dataSet )
D = list(map( set, dataSet ))
L1, suppData = scanD( D, C1, minSupport )#构建初始频繁项集,所有项集只有一个元素
L = [ L1 ]
k = 2 #最初的L1中每个项集只包含一个元素,新生成的项集应该含有2个元素,所以 k=2
while (len( L[ k - 2 ]) > 0):
Ck = aprioriGen( L[ k - 2 ], k ) #产生新的候选项集
Lk, supK = scanD( D, Ck, minSupport ) #返回频繁项集和支持度信息
suppData.update( supK )
L.append( Lk )
k += 1#新生成的项集中的元素个数应不断增加
return L, suppData #返回所有满足条件的频繁项集的列表,所有候选项集的支持度信息
if __name__ == '__main__':
myDat = loadDataSet()
L, suppData = apriori( myDat, 0.5 )
print ("频繁项集L:", L)
print ("所有候选项集的支持度信息:", suppData)
频繁项集L: [[frozenset({5}), frozenset({2}), frozenset({3}), frozenset({1})], [frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5}), frozenset({1, 3})], [frozenset({2, 3, 5})], []]
所有候选项集的支持度信息: {frozenset({1}): 0.5, frozenset({3}): 0.75, frozenset({4}): 0.25, frozenset({2}): 0.75, frozenset({5}): 0.75, frozenset({1, 3}): 0.5, frozenset({2, 5}): 0.75, frozenset({3, 5}): 0.5, frozenset({2, 3}): 0.5, frozenset({1, 5}): 0.25, frozenset({1, 2}): 0.25, frozenset({2, 3, 5}): 0.5}
注意:aprioriGen(L2,3)由二元素的两两合并为三元素的,只有当L2中元素的第一个成员相同才合并。aprioriGen(L3,4)由三元素的两两合并为四元素的,只有当L3中元素的第一二个成员相同才合并。[ : k-2]的作用就在于此!
挖掘关联规则:从上面的购物清单发现,购买豆奶的同时购买葡萄酒的概率往往较高,即{豆奶}—>{葡萄酒},但反过来不一定成立。那么可以产生多少条关联规则呢?首先基于该频繁项集生成一个可能的规则列表,然后测试每条规则的可信度,如可信度不满足最小值要求,则去掉该规则。类似于前面讨论的频繁项集生成,如果某条规则并不满足最小可信度要求,那么该规则的所有子集也不会满足最小可信度要求。如图:{0,1,2}—>{3}是一条低可信度的关联规则,则所有包含3为结论的规则都是低可信度的。
#挖掘关联规则,根据频繁项集和最小可信度生成规则
'''
[[frozenset({5}), frozenset({2}), frozenset({3}), frozenset({1})],
[frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5}), frozenset({1, 3})],
[frozenset({2, 3, 5})], []]
'''
def generateRules(L, supportData, minConf=0.7):
bigRuleList = []
for i in range(1, len(L)):
for freqSet in L[i]:
H1 = [frozenset([item]) for item in freqSet]#频繁项集中包含的所有元素
if i > 1: #如果频繁项集中的元素个数大于2,需要进一步合并
rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
else:
calcConf(freqSet, H1, supportData, bigRuleList, minConf)
return bigRuleList
def calcConf(freqSet, H, supportData, brl, minConf=0.7):
'''
计算规则的可信度,返回满足最小可信度的规则
H(frozenset):频繁项集中所有的元素
brl(tuple):满足可信度条件的关联规则
'''
prunedH = []
for conseq in H:
conf = supportData[ freqSet ] / supportData[ freqSet - conseq ]
if conf >= minConf:
print (freqSet - conseq, '-->', conseq, 'conf:', conf)#输出关联规则
brl.append(( freqSet - conseq, conseq, conf ))
prunedH.append(conseq)
return prunedH
def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
'''
这里包含Apriori的一个核心思想:
{0,1,2}—>{3}是一条低可信度的关联规则,则所有包含3为结论的规则都是低可信度的。用来减少计算量
H(frozenset):频繁项集中的所有元素,即可以出现在规则右部的元素
brl(tuple):生成的规则
'''
m=len(H[0])
if len(freqSet) > m + 1:
Hmp1 = aprioriGen( H, m + 1 )#生成候选项集,H={2}{3}{5}
Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
#calcConf可以得到后件,即通过关联规则能够推出的结果,这个地方体现了Apriori算法的一个核心思想
if len( Hmp1 ) > 1: #Hmp1是一个列表,里面包含{2,5},{2,3}
rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)
if __name__ == '__main__':
myDat = loadDataSet()
L, suppData = apriori(myDat, 0.5)
rules = generateRules(L, suppData, minConf=0.7)
print ('rules:\n', rules)
输出关联规则结果:
frozenset({5}) --> frozenset({2}) conf: 1.0 frozenset({2}) --> frozenset({5}) conf: 1.0 frozenset({1}) --> frozenset({3}) conf: 1.0 rules: [(frozenset({5}), frozenset({2}), 1.0), (frozenset({2}), frozenset({5}), 1.0), (frozenset({1}), frozenset({3}), 1.0)]
参考内容:https://www.manning.com/books/machine-learning-in-action