上一篇博客介绍了Apriori算法,该算法有一个致命的缺陷,就是在产生频繁模式完全集前需要对数据库进行多次扫描,同时产生大量的候选频繁集,这就使得Apriori算法时间和空间复杂度过大。基于上述原因,韩家炜等人提出了FP-tree算法,该算法仅需要扫描两次数据库,就可以完成Apriori算法的功能。在算法中使用了特殊的数据结构频繁模式树(Frequent Pattern Tree),由频繁项头表和项前缀树构成(这里也用到了Apriori的一些性质,比如:集合A非频繁,那么包含结合A的所有集合都是非频繁,如果不知道,请看上一篇博客的推导过程,这里不在叙述了。。)。这里先举一个例子说明:
如上图所示,就是一个FP-Tree,看起来貌似和我们以前数据结构学习的树不大一样,多了一个频繁项头表(Header Table),那么他是如何构建树的哪?
第一步:扫描数据库,找出并记录大于最小支持度的所有事物项,并且初始化频繁项头表。
第二步:扫描数据库,对数据集中每一个事物按照事物项的频次的大小降序排列。在对该事物中的每一个事物项做如下操作:
1,树更新:如果根节点已包含该事物的第一个事物项,则事物项的值+1,否则新建一个节点。
2,频繁项头表更新:如果频繁项头表已经包含该事物项,则找出链表的末尾位置,加入该项;否则,直接加入
3,重复1,2两步操作,知道该事物遍历完成,接着进行下一个事物同样的操作。。
确实不太好用语言来描述,但是如果根据图和文字理解,就会明白是什么意思,FP-tree不仅要描述一个事务中不同事务项之间的联系(一个行数据中事务项的联系),还要描述所有事物中,同类型事务项的联系(比如:所有名称为x的事务项的联系是一个链表,x:4->x:3->x:1)。下图是一个简单的构建过程:
上图中首先对原始数据进行频繁集过滤,然后按照支持度降序排列(这个很重要),{z,r}是最先加入到根节点当中,当{z,x,y,s,t}再加入时,z可以实现两颗子树的公用,因此z节点的值记为2,依次类推直到树的结构建成为止。
下面看看原有的代码是如何实现的,说实话我看了好多遍才恍然大悟(代码还是要认真的去读的,实现的代码很多种,这里还是Machine in Action中的代码)。`”’
Created on Jun 14, 2011
FP-Growth FP means frequent pattern
the FP-Growth algorithm needs:
1. FP-tree (class treeNode)
2. header table (use dict)
This finds frequent itemsets similar to apriori but does not
find association rules.
''' Created on Jun 14, 2011 FP-Growth FP means frequent pattern the FP-Growth algorithm needs: 1. FP-tree (class treeNode) 2. header table (use dict) This finds frequent itemsets similar to apriori but does not find association rules. @author: Peter '''
class treeNode:
def __init__(self, nameValue, numOccur, parentNode):
self.name = nameValue#事物项名称
self.count = numOccur#事物项在数据集中出现的次数
self.nodeLink = None#同类事物项之间的链接
self.parent = parentNode #该事物项的父节点
self.children = {} #该事物项的子节点
def inc(self, numOccur):
self.count += numOccur
def disp(self, ind=1):
print ' '*ind, self.name, ' ', self.count
for child in self.children.values():
child.disp(ind+1)
def createTree(dataSet, minSup=1): #create FP-tree from dataset but don't mine
headerTable = {}#初始化频繁项头表
#go over dataSet twice
for trans in dataSet:#first pass counts frequency of occurance
for item in trans:
headerTable[item] = headerTable.get(item, 0) + dataSet[trans]#第一遍扫描数据集,并且记录每一个事物项的频次
for k in headerTable.keys(): #去除频次小于门限值的事物项
if headerTable[k] < minSup:
del(headerTable[k])
freqItemSet = set(headerTable.keys())#频繁事物集
#print 'freqItemSet: ',freqItemSet
if len(freqItemSet) == 0: return None, None #if no items meet min support -->get out
for k in headerTable:
headerTable[k] = [headerTable[k], None] #频繁项头表
#print 'headerTable: ',headerTable
retTree = treeNode('Null Set', 1, None) #create tree
for tranSet, count in dataSet.items(): #第二次扫描数据集
localD = {}
for item in tranSet: #每一个事物按照事物项的频次降序排序
if item in freqItemSet:#仅仅取出大于门限值的频繁项
localD[item] = headerTable[item][0]
if len(localD) > 0:
orderedItems = [v[0] for v in sorted(localD.items(), key=lambda p: p[1], reverse=True)]#根据key的关键字排序
updateTree(orderedItems, retTree, headerTable, count)#
#更新树和表
return retTree, headerTable #return tree and header table
def updateTree(items, inTree, headerTable, count):
if items[0] in inTree.children:#如果节点的子节点已经包含该事物,则记数增加1
inTree.children[items[0]].inc(count) #incrament count
else: #否则,新建一个子节点
inTree.children[items[0]] = treeNode(items[0], count, inTree)
if headerTable[items[0]][1] == None: #如果项头表没有加入该类型的频繁项,则加入一个
headerTable[items[0]][1] = inTree.children[items[0]]
else:
updateHeader(headerTable[items[0]][1], inTree.children[items[0]])#如果表中已经包含该频繁项,则找出链表中最后一个位置,加入
if len(items) > 1:#call updateTree() with remaining ordered items
updateTree(items[1::], inTree.children[items[0]], headerTable, count)#继续对下一个频繁事物项操作
def updateHeader(nodeToTest, targetNode): #this version does not use recursion
while (nodeToTest.nodeLink != None): #Do not use recursion to traverse a linked list!
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNode
def loadSimpDat():
simpDat = [['r', 'z', 'h', 'j', 'p'],
['z', 'y', 'x', 'w', 'v', 'u', 't', 's'],
['z'],
['r', 'x', 'n', 'o', 's'],
['y', 'r', 'x', 'z', 'q', 't', 'p'],
['y', 'z', 'x', 'e', 'q', 's', 't', 'm']]
return simpDat
minSup = 3
simpDat = loadSimpDat()
initSet = createInitSet(simpDat)
myFPtree, myHeaderTab = createTree(initSet, minSup)
myFPtree.disp()
myFreqList = []
mineTree(myFPtree, myHeaderTab, minSup, set([]), myFreqList)