您的位置:首页 > 编程语言 > Python开发

FP-growth算法高效发现频繁项集(Python代码)

2016-05-06 16:04 826 查看

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

1. 介绍

我们都有过这样的经历,在百度搜索里输入一个单词或者单词一部分的时候,搜索引擎会自动补全查询词项,比如当我输入“人工”两字的时候,度娘第一个反应是:咦,这人是不是想找人工受精相关的信息呢?所以就有了下面的这幅图:



图1 示例图片

FP-growth算法是伊利罗伊香槟分校的韩嘉炜教授于2004年[1]提出的,它是为了解决Apriori算法每次增加频繁项集的大小都要遍历整个数据库的缺点,特别是当数据集很大时,该算法执行速度要快于Apriori算法两个数量级。FP-growth算法的任务是将数据集存储在一个特定的称为FP树的结构之后发现频繁项集或者频繁项对,虽然它能够高效地发现频繁项集,但是不能用来发现关联规则,也就是只优化了Apriori算法两个功能中的前一个功能。
FP-growth算法只需要对数据集进行两次扫描,所以即使数据集很大时也不会花费太多的时间在扫描数据上,它发现频繁项集的基本过程如下:
1)构建FP树
2)从FP树中挖掘频繁项集

2. 算法详解

2.1 用FP树编码数据集

FP-growth算法将数据存储在一个称为FP树的紧凑数据结构中,它与计算机科学中的其他树的结构类似,但是它通过链接来链接相似元素,被连起来的元素可以看做一个链表,如下图:



图 2 FP树的结构示意图

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



图3 用于发现图2中FP树的事务数据样例

第一列是事务的ID,第二列是事务中的元素项,在图2中z出现了5次,而{r,z}项支出项了一次,所以z一定自己本身或者和其他的符号一起出现了4次,而由图2同样可知:集合{t,s,y,x,z}出现了2次,集合{t,r,y,x,z}出现了1次,所以z一定本身出现了1次。看了图3可能会有疑问,为什么在图2中没有p,q,w,v等元素呢?这是因为通常会给所有的元素设置一个阈度值(Apriori里的支持度),低于这个阈值的元素不加以研究。暂时不理解没关系,看了下面的内容可能会对这一段的内容有比较好的理解。

2.1.1 构建FP树

构建FP树是算法的第一步,在FP树的基础之上再对频繁项集进行挖掘。为了构建FP树,要对数据集扫描两次,第一次对所有元素项出现次数进行计数,记住如果一个元素不是频繁的,那么包含这个元素的超集也不是频繁的,所以不需要考虑这些超集,第二遍的扫描只考虑那些频繁元素。
除了图2给出的FP树之外,还需要一个头指针表来指向给定类型的第一个实例。利用头指针表可以快速访问FP树中一个给定类型的所有元素,发现相似元素项,如下图所示:



图 4 带头指针的FP树

头指针表的数据结构是字典,除了存放头指针元素之外,还可以存放FP中每类元素的个数。第一次遍历数据集得到每个元素项出现的频率,接下来去掉不满足最小值支持度的元素项,在接下来就可以创建FP树了,构建时,将每个项集添加到一个已经存在的路径中,如果该路径不存在,则创建一个新的路径。每个事务都是一个无序的集合,然而在FP树中相同项只会出现一次,{x,y,z}和{y,z,x}应该在同一个路径上,所以在将集合添加到树之前要对每个集合进行排序,排序是基于各个元素出现的频率来进行的,使用图4头指针表中单个元素的出现值,对图3中的数据进行过滤,重排后的新数据如下:



图5 移除非频繁项,重新排序后的事务表

现在,就可以构建FP树了,从空集开始,向其中不断添加频繁项集。过滤,排序后的事务依次添加到树中,如果树中已有现有元素,则增加该元素的值;如果元素不存在,则添加新分枝。图5中事务表前两条事务添加的过程如下图所示:



图 6 FP构建过程示例图

现在应该大致知道数据集转换成FP树的思想了吧。

2.2 从FP树中挖掘频繁项

有了FP树之后就可以抽取频繁项集了,思想与Apriori算法大致一样,从单元素项集开始,逐步构建更大的集合,只不过不需要原始的数据集了。
从FP树中抽取频繁项集的三个基本步骤:
1)从FP树中获得条件模式基;
2)利用条件模式基,构建一个条件FP树;
3)迭代重复步骤(1)(2)直到树只包含一个元素项为止

2.2.1抽取条件模式基

条件模式基是以所查找元素项为结尾的路径集合,每一条路径包含一条前缀路径和结尾元素,图4中,符号r的前缀路径有{x,s}、{z,x,y}和{z},每一条前缀路径都与一个数据值关联,这个值等于路径上r的数目,下表中列出单元素频繁项的所有前缀路径。



图7 每个频繁项的前缀路径

前缀路径将被用于构建条件FP树。为了获得这些路径,可以对FP树进行穷举式搜索,直到获得想要的频繁项为止,但可以使用一个更为有效的方法加速搜索过程。可以用先前的头指针表来创建一种更为有效的方法,头指针表中包含相同类型元素链表的起始指针。一旦到达了每一个元素项,就可以上溯这棵树直到根节点为止。

2.2.2 创建条件FP树

对于每一个频繁项,都要创建一个条件FP树,将上面的条件模式基作为输入,通过相同的建树方法来构建这些条件树,然后递归地发现频繁项、发现条件模式基,以及发现另外的条件树。举个例子,假定为频繁项t创建一个条件FP树,然后对{t,y},{t,x}、...重复该过程。元素项t的条件FP树的构建过程如下:





图 8

s,r虽然是条件模式基的一部分,且单独看都是频繁项,但是在t的条件树中,他却是不频繁的,分别出现了2次和一次,小于阈值3,所以{t,r},{t,s}不是频繁的。接下来对集合{t,z},{t,x},{t,y}来挖掘对应的条件树,会产生更复杂的频率项集,该过程重复进行,直到条件树中没有元素 为止,停止。

3 代码实现

定义树的结构:
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)


创建树:

#dataSet是记录,minSup是最小支持度
def createTree(dataSet,minSup=1):
#对每个元素进行计数
headerTable = {}
for trans in dataSet
for item in trans:
headerTable[item] = headerTable.get(item,0) + dataSet[trans]

#删除项集大小为1的非频繁项集,根据apriori原则,包含该非频繁项集的项集都不可能是频繁项集
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:
headereTable[k] = [headerTable[k],None]
#创建根节点
retTree = treeNode('Null Set',1,None)
#得到删除非频繁k=1的项的 项集,并以字典树的形式插入树里。
for tranSet, count in dataSet.items():
localID = {}
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


更新HeaderLink的链:

def updateHeader(nodeToTest,targetNone):
while(nodeToTest.nodeLink != None):
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNone


测试数据:

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

4. 参考文献

[1] Machine Learning in Action
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: