FP-growth算法高效发现频繁项集

  • ​FP-growth算法高效发现频繁项集​
  • ​​引言​​
  • ​​基本概念​​
  • ​​构建FP树​​
  • ​​挖掘频繁项​​
  • ​​代码详解​​
  • ​​总结​​


引言

在关联分析中,频繁项集的挖掘最常用到的就是Apriori算法。Apriori算法是一种先产生候选项集再检验是否频繁的“产生-测试”的方法。这种方法有种弊端:当数据集很大的时候,需要不断扫描数据集造成运行效率很低。

而FP-Growth算法就很好地解决了这个问题。它的思路是把数据集中的事务映射到一棵FP-Tree上面,再根据这棵树找出频繁项集。FP-Tree的构建过程只需要扫描两次数据集。 ——《机器学习实战》

基本概念

FP-growth算法将数据存储在一种称为FP树的紧凑数据结构中。FP(Frequent Pattern)通过链接来连接相似元素,被连起来的元素项可以看成一个链表。

同搜索树不同的是,一个元素项可以在一棵FP树中出现多次。FP树会存储项集的出现频率,而每个项集会以路径的方式存储在树中。存在相似元素的集合会共享树的一部分。只有当集合之间完全不同时,树才会分叉。树节点上给出集合中的单个元素及其在序列中的出现次数,路径会给出该序列的出现次数。相似项之间的链接即节点链接,用于快速发现相似项的位置。

FP-growth算法首先构建FP树,然后利用它来挖掘频繁项集。为构建FP树,需要对原始数据集扫描两遍。第一遍对所有元素项的出现次数进行计数。第二遍扫描只考虑那些频繁元素。

构建FP树

从FP树中抽取频繁项集的三个基本步骤如下:

  1. 从FP树中获得条件模式基;
  2. 利用条件模式基,构建一个条件FP树;
  3. 迭代重复步骤1步骤2,直到树包含一个元素项为止。
FP树建立:
将样本中统计出所有元素的出现次数并筛选符合条件要求的做频繁项集
定义一棵树的空根节点
定义一条频繁项集的头指针
for 样本:
以频繁项集筛选一个样本中的频繁项并排序(降序)作频繁项
for 频繁项:
if 元素存在树中:此节点count+1
else:添加元素
if 此头指针有子指针:循环插到最后
else:
直接插在头指针后

挖掘频繁项

对于每一个频繁项,都要创建一棵条件FP树。可以使用刚才发现的条件模式基作为输入数据,并通过相同的建树代码来构建这些树。例如,对于r,即以“{x, s}: 1, {z, x, y}: 1, {z}: 1”为输入,调用函数createTree()获得r的条件FP树;对于t,输入是对应的条件模式基“{z, x, y, s}: 2, {z, x, y, r}: 1”。

FP-growth算法高效发现频繁项集_数据集

条件FP树:
头指针排序(升序)
建立储存列表set
for 头指针的链表:
回溯添加此元素多个节点到根节点中间的所有元素,存放到set
建立此元素的FP树

输出结果

代码详解

# coding=utf-8
import numpy as np

class treeNode:
def __init__(self, nameVale, numOccur, parentNode):
self.name = nameVale
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 laodSimpDat():
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', 'q'],
['y', 'z', 'x', 'e','q', 's', 't', 'm']]
return simpDat

def createInitSet(dataList):
retDict = {}
for i in dataList:
retDict[frozenset(i)] = 1;
return retDict

#构建树
def createTree(dataSet, minSup = 1):
headerTable = {}
#单字母出现的频率
for i in dataSet:
for j in i:
headerTable[j] = headerTable.get(j, 0) + dataSet[i]

#筛选出高频字母
for k in 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]
#print headerTable
#建立头节点树根
reTree = treeNode('Null set', 1, None)
#.items():以列表返回可遍历的(键, 值) 元组数组
#将所有数据集里的频繁数据排序更新
for tranSet, count in dataSet.items():
localD = {}
#print 'tranSet:',tranSet
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)]
#print 'orderedItems:',orderedItems
updateTree(orderedItems, reTree, headerTable, count)
#print 'localD:', localD,'\n'

return reTree, headerTable

#更新树
def updateTree(items, inTree, headerTable, count):#待排序序列,父节点,字符集,个数集
#print 'items:',items
#字符已经存在,就加上count就行
if items[0] in inTree.children:
inTree.children[items[0]].inc(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:#存在循环
updateTree(items[1::], inTree.children[items[0]], headerTable, count)

#加长链表
def updateHeader(nodeToTest, targetNode):#头节点和孩子
#指针循环
while (nodeToTest.nodeLink != None):
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNode

#回溯搜索储存父节点
def ascendTree(leafNode, prefixPath):
if leafNode.parent != None:
prefixPath.append(leafNode.name)
ascendTree(leafNode.parent, prefixPath)

#给出头指针,搜索FP树中所有到底此头指针的链接
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 condPats

#每个频繁项都建立一颗FP树
def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
#将头指针按照数量排序
bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1])]
print bigL
for basePat in bigL:
newFreqSet = preFix.copy()
newFreqSet.add(basePat)
#print 'finalFrequent Item: ',newFreqSet
freqItemList.append(newFreqSet)
condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
#print 'condPattBases :',basePat, condPattBases
#创建频繁项basePat的FP树
myCondTree, myHead = createTree(condPattBases, minSup)
#print 'head from conditional tree: ', myHead
if myHead != None:
#print 'conditional tree for: ',newFreqSet
#循环构建FP树
mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)


def main():
dataArr = laodSimpDat()
dataList = createInitSet(dataArr)
print dataList
myFPtree, myheaderTab = createTree(dataList, 3)
print myFPtree.disp()
freqItems = []
mineTree(myFPtree, myheaderTab, 3, set([]), freqItems)
print freqItems

if __name__ == "__main__":
main()

结果演示:

Null set   1
x 1
s 1
r 1
z 5
x 3
y 3
s 2
t 2
r 1
t 1
r 1
None
[set(['y']), set(['y', 'x']), set(['y', 'z']), set(['y', 'x', 'z']), set(['s']), set(['x', 's']), set(['t']), set(['y', 't']), set(['x', 't']), set(['y', 'x', 't']), set(['z', 't']), set(['y', 'z', 't']), set(['x', 'z', 't']), set(['y', 'x', 'z', 't']), set(['r']), set(['x']), set(['x', 'z']), set(['z'])]

总结

算法的优点:一般要快于Apriori算法

缺点:实现比较困难,在某些数据集上性能会下降

相对Apriori算法来说,FP-growth算法还是比较快的,也继承了Apriori的一个剪枝特点,就是对非频繁元素的处理,此元素不是频繁的,那么包含此元素的集合也不是频繁的。还有就是对树的建立比较难理解,有是更新又是循环递归的。