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

来源:互联网 发布:易云网络人气 编辑:程序博客网 时间:2024/05/18 01:52

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 simpDatdef createInitSet(dataSet):    retDict = {}    for trans in dataSet:        retDict[frozenset(trans)] = 1    return retDict

4. 参考文献

[1] Machine Learning in Action








0 0
原创粉丝点击