FP-growth算法跟apriori算法一样,都是用来发现频繁集和关联规则的,不过由于FP-growth算法用了树结构,效率比apriori快了很多。FP-growth算法中选取频繁集时,只需要对整个数据集扫描两次,而apriori算法对于每个候选频繁集就会对数据集进行扫描,可见FP-growth算法的效率之高。下面,博主就为大家介绍一下FP-growth算法。
一、FP树结构
FP-growth算法中的树称为FP树,那么FP树的结构是怎样的呢?我们用下面的例子来说明,提前声明一下,以下的例子均来自于《机器学习实战》一书。
上图是6个事务,可以简单地理解为6条交易,最后一列是频繁的元素,我们将用最后一列来构建FP树,如下图:
可以看到, 这个FP树分为两个部分。右边的树状图暗含了每条交易记录。这棵树的建立方式如下:
1. 对于001号交易记录{z,r},从根节点开始,观察根节点的子节点中是否有z这个节点,如果没有就创建一个,然后给z赋值为1,代表z出现的次数;然后再观察以z为根节点的子节点是否有r这个节点,如果没有就创建一个,然后给r赋值为1,代表r出现的次数
2. 对于002号交易记录{z,x,y,s,t},从根节点开始,观察根节点的子节点中是否有z这个节点,发现z已经存在,那么z的值加1,代表z出现了两次;然后再观察以z为根节点的子节点中是否有x这个节点,没有则创建一个,然后给x赋值为1,对于y,s,t依次进行
3. 对于003号交易记录{z},从根节点开始,观察根节点的子节点中是否有z这个节点,发现z已经存在,那么z的值加1
4. 对于004号交易记录{x,s,r},从根节点开始,观察根节点的子节点中是否有x这个节点,如果就创建一个并赋值为1,然后对s和r依次进行
5. 对于005号交易记录{z,x,y,r,t},在搜索树的过程中发现已经存在z,x,y的路径,y的子节点下没有r,于是在y的子节点中构建一个名字为r的节点,在r的子节点中构建一个名字为t的节点
6. 对于006号交易记录{z,x,y,s,t},在搜索树的过程中发现已经存在这条路径,则只会改变值不会改变树的结构。
以上就是树的构建过程,现在来讲解左边的头指针表。
头指针表的记录了每个频繁元素的名字以及其出现的总次数,其指针指向相同名字的元素,用来更快的搜索相同名字元素。
二、FP树的构建
接下来就讲解一下FP树的代码。
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
def createInitSet(dataSet):
retDict = {}
for trans in dataSet:
retDict[frozenset(trans)] = 1
return retDict
上面两个函数是数据预处理过程,处理的结果是将所有的交易记录存放在字典中,字典的键是交易记录的frozenset形式,值为1。
下面的代码定义了一个树的数据结构:
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)
其中disp()函数是用来以文本形式展示树,如下图:
缩进就代表着子树。
def createTree(dataSet,minSup=1):
headerTable = {}
for trans in dataSet: #第一次扫描
for item in trans:
headerTable[item] = headerTable.get(item,0) + dataSet[trans]
for k in list(headerTable.keys()):
if headerTable[k] < minSup:
del(headerTable[k])
freqItemSet = set(headerTable.keys())
if len(freqItemSet) == 0:
return None,None
for k in headerTable:
headerTable[k] = [headerTable[k],None] #第二个元素用来连接同名节点
retTree = treeNode('Null Set',1,None)
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)]
updateTree(orderedItems,retTree,headerTable,count)
return retTree,headerTable
def updateTree(items,inTree,headerTable,count):
if items[0] in inTree.children:
inTree.children[items[0]].inc(count)
else:
inTree.children[items[0]] = treeNode(items[0],count,inTree)
#FP树中添加了节点,因此要判断头节点表是否需要更新
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:
updateTree(items[1::],inTree.children[items[0]],headerTable,count)
def updateHeader(nodeToTest,targetNode):
while nodeToTest.nodeLink != None:
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNode
上面的是创建树的代码,第一次扫描的过程中删除了非频繁元素,第二次扫描则用频繁元素创建了树。回过头去看上面介绍的树的创建过程会发现,每条交易记录中元素的顺序显然会影响树的形状,因此我们需要对元素进行排序,排序的依据就是它们出现的次数,第二次扫描中的orderedItems即用来保存排序后的元素排列,然后使用orderedItems来更新树。
在树的更新过程中,首先判断当前树的子节点中是否有第一个元素,如果有,让其加1就好了;如果没有,就创建一个以该元素为根节点的子树,此时头指针表也需要更新。如果头指针表不存在连接,就让其连接到该元素;如果存在连接,就让该连接中的最后一个节点再连接到这第一个元素。
运行上一段代码会得到下面这样的结果:
三、条件模式基
有了FP树,现在我们就要从中挖掘频繁项集了。在挖掘之前,先介绍一下条件模式基的概念。条件模式基是以所查按照元素项为结尾的路径,而介于所查找元素与树根节点之间的路径称为前缀路径(prefix path)。抽取条件模式基的代码如下:
def ascendTree(leafNode,prefixPath):
if leafNode.parent != None:
prefixPath.append(leafNode.name)
ascendTree(leafNode.parent,prefixPath)
def findPrefixPath(basePat,treeNode):
condPats = {}
while treeNode != None:
prefixPath = []
ascendTree(treeNode,prefixPath)
if len(prefixPath) > 1:
condPats[frozenset(prefixPath[1:])] = treeNode.count
treeNode = treeNode.nodeLink
return condPat
运行结果如图:
四、创建条件FP树
FP-growth算法中抽取频繁集的思路跟apriori算法一样,都是从单元素项集开始,然后逐步构建更大的集合。而一个频繁集,在这里用一个树来表示,称作条件FP树。条件FP树用条件模式基作为输入数据,然后递归的发现频繁项、发现条件模式基,以及发现另外的条件树。代码如下:
def mineTree(inTree,headerTable,minSup,preFix,freqItemList):
bigL = [v[0] for v in sorted(headerTable.items(),key=lambda p:p[1][0])]
for basePat in bigL:
newFreqSet = preFix.copy()
newFreqSet.add(basePat)
freqItemList.append(newFreqSet)
condPattBases = findPrefixPath(basePat,headerTable[basePat][1])
myCondTree,myHead = createTree(condPattBases,minSup)
if myHead != None:
mineTree(myCondTree,myHead,minSup,newFreqSet,freqItemList)
运行后输出如下: