对FP-Growth算法构建原理、条件模式基的理解

在学习关联规则挖掘的时候,查阅了一些相关的资料。大多数博客都比较相似,介绍每一步的算法流程。我也就是把我自己学习算法过程中的一些问题和对问题的理解记录一下,希望可以加深大家对该算法的理解。

本篇可能不会详细讲解FP树的构建过程或是算法流程,而是会对算法中的一些关键点给出自己的理解,分析算法为什么要这么做,这么做的目的是什么。从而可以加深一些对算法的深入理解。同时也需要对Apriori算法有个大致的理解,才能进行对比理解到为什么要建FP树的原因。

**

FP算法简介

**
FP-growth算法将数据存储在一种称为FP树的紧凑数据结构中。FP代表频繁模式(Frequent Pattern)。一棵FP树看上去与计算机科学中的其他树结构类似,但是它通过链接(Link)来连接相似元素,被连起来的元素项可以看成一个链表。

如图给出一个FP树的例子(该例子与Machine Learning in Action 一致)

原始数据集为,及书本上过滤及重排序后的事务为:
对FP-Growth算法构建原理、条件模式基的理解_第1张图片
根据该数据集生成的FP树为:
对FP-Growth算法构建原理、条件模式基的理解_第2张图片
FP树会存储项集的出现频率,而每个项集会以路径的方式存储在树中。存在相似元素的集合会共享树的一部分。只有当集合之间完全不同时,树才会分叉。树节点上给出集合中的单个元素及其在序列中的出现次数,路径会给出该序列的出现次数。

相似项之间的链接即节点链接(node link),用于快速发现相似项的位置。

于是我们可以对FP树有一个大致的认识:
首先需要设定一个头指针表来指向给定类型的第一个实例
利用头指针表可以快速访问FP树种的一个给定类型的所有元素

FP树遍历两次数据集:
1-统计每个元素项出现的频率
2-遍历每个记录来构建FP树,读入每个项集并将其添加到一条已经存在的路径中

在此之前,我们先对以上构建FP树中的一些思想进行总结。构建FP树的思想即是:

我们知道由于Apriori算法在找到某一频繁项集后,都要遍历一遍原始数据集来查找该项集在数据集中出现的次数。这就导致了每个项集都要对原始数据集全部扫描一下,这样在数据量较大时是非常麻烦的。这也是Apriori算法最大的弊端。那我们想要对此进行改进,改进的算法即是想如何可以更高效的确定某一项集在原始数据集中出现的次数

由此想到了可以利用树形结构来表示,为什么呢?

因为树的每一条分支可以代表一条路径,即从根节点到某一节点之间出现的所有节点。于是我们可以对原始数据集进行合并修整,对有公共前缀的记录进行整合,并利用节点的数值标明,从根节点到该节点之间所有元素同时出现的次数。这样对于某一目标节点,我们就可以通过遍历其父节点(找到前缀路径)来快速找到与其共现的元素集合。

我们可以对FP树的作用总结如下:
FP树相当于对原始数据集的整合,构建FP树的目的是为了可以快速的发现与目标元素共现的元素集合,而不是在海量的数据中利用全扫描来盲目的寻找。根下一节在条件模式基下建FP树的联系其实就是,例如对于上图中的headerTable表{z,r,x,y,s,t},在Apriori算法中,如果我们希望找两项集,我们想要对其中的元素两两合并比如{z,r},{z,x},…{x,y}…{s,t}然后分别把每个都去原始数据集中查找看出现次数,这种是很费时间的,因为有些项可能根本就不会共现。所以构建FP树的目的就是把共现过的元素放在同一路径上表示,这样对于某一元素,我们不需要把它跟其他所有剩余的元素都连接,而只用把它跟它共现过的元素进行拼接,这也就是在条件模式基下找元素前缀路径的作用,只要在前缀路径中的元素,才会与目标元素连接,这样就可以不用做无用功,改进了Apriori算法的弊端。

利用条件模式基找频繁项集

我们的算法最终目的,还是为了去挖掘频繁项集。接下来便讨论FP-Growth算法中更为重要的思想——利用条件模式基去挖掘频繁项集。

上文已经理解了FP树的作用,其实我感觉FP树不是为了找频繁项集而产生的,它的目的更多的是在对原始数据的处理上,是为了方便我们在找频繁项集时,可以专注于那些与目标元素(项集)共现过的元素集合,而不用考虑那些可能并不会共现的元素。

接下来我们讨论一下headerTable的作用。headerTable表中不仅保存了元素的出现次数,还有该元素在树中的第一个元素节点。
之前讲了那么多,说FP树可以找到与目标元素共现过的元素的频繁项集等等。那还有一个问题就是,当找到了某一元素的频繁项集,如果不去扫描原始数据集,如何得知该频繁项集的出现次数呢?
我觉得这个点就在headerTable里。有人会说不是应该在FP树的节点后面的数字表示吗?我的理解倾向于FP树中节点上的数字,更多代表的只是说这某路径出现的次数,根本还是对数据进行的刻画。比如在后面条件模式基中找前缀路径时,根据节点的计数我们就可以得知某条前缀路径的出现次数,从而可以对某一单个元素进行计数求和(也就是得出headerTable表中某一元素的全部出现次数)。

那为什么是在headerTable表中呢?因为headerTable表保存了某一元素出现的全部次数。在构建第一棵FP树时,我们可以假定headerTable表中保存的是“当前提条件为空时,各元素的出现次数”。现在我们假设希望寻找元素t的多项集,
同样以上图中的树为例。

1.按照上文所说,我们需要先从树中找出前缀路径,从而找出与t共现过的元素集合:

t:{z,x,y,s}:2, {z,x,y,r}:1

2.接着统计这两天记录的次数,构建headerTable表(这里只是为了从逻辑层面来理解,先不考虑表中保存的链接节点)。可以得出headerTable表中各元素出现的次数为:

headerTable: {z:3, x:3, y:3, s:2, r:1}

这即是表示“当t元素出现的前提下,z,x,y,s,r各元素出现的概率”。那么我们是否可以得出项集{t,z}的出现次数是3,{t,x}的出现次数是3,{t,s}的出现次数是2呢?
我觉得是可以的。因为对目标元素找前缀路径时我们知道,该路径上的元素即是与目标元素共现过的元素集合。那么当我们将在多条路径上出现了的目标元素找到其前缀路径后,就相当于找出了在原始数据集中包含该目标元素(或元素项)的记录。既然这些记录中包含了目标元素,那么必然目标元素可以与前缀路径中的元素两两组合,且其数值等于对前缀路径中各元素整合求和后的计数值,也就是headerTable中各元素的数值。

3.我们在找完t的两项集后,希望继续去挖掘三项集。怎么找呢?同样的是按之前的思想。如果是按Apriori那就是对{z,x,y,s,r}两两组合,那么FP算法就是与2步骤同样的思想,我们希望在连接前,先找到与目标元素共现过的元素集合,例如我们希望先找到z是该与x连接成{t,z,x}还是与y组成{t,z,y}。这就需要我们根据1.中的两条记录,继续构造FP树,如下:(所以我觉得FP树就是用来找与目标元素共现过的元素集合,从而避免了Apriori算法中的直接两两连接):
对FP-Growth算法构建原理、条件模式基的理解_第3张图片
例如上图根据条件FP树我们可以看出不存在三项集{t,s,r},(回顾原始数据集的确没有),于是我们在搜索三项集时,就不用对该项集进行查找。

以上虽然没有讲到什么是条件模式基,但是其实大体的思路已经呈现出来了。拿什么是条件模式基呢?
条件模式基概念解释是以所查元素项为结尾的路径集合,每条路径其实都是一条前缀路径。其实也就是我们上文步骤1.所找到的目标元素的两条前缀路径。而抽取条件模式基的目的,就是我们上文说的找到目标元素的共现元素集合,从而使目标元素只与共现过的元素进行拼接组合。

条件FP树也即为上图中对目标元素r构建的条件FP树。条件树的作用也是为了该树对应的headerTable表中的元素进行拼接时,可以使得元素只与自己共现过的元素进行拼接。

总结

于是可以对以上总结一下各点的思路:
1.构造FP树是将原始数据进行合并整合,将共现过的元素放在树的同一分支下,这样可以快速找到与目标元素共现过的元素集合
2.寻找前向路径即是寻找与目标元素共现过的元素集合,这样避免了Apriori算法中需要把目标元素与剩余所有元素进行拼接组合
3.创建条件模式基的过程即是去发现双项集、三项集、四项集等的过程。
每个条件模式基中的headerTable表中保存的元素的计数,即表示了在以目标元素为条件出现的前提下,该元素出现的次数。
例如以目标元素{r}为条件出现的前提下,构造的headerTable表中的元素计数为{z:2,x:2,s:1,y:1,t:1}
即表示当r出现的前提下,z出现的次数为2。相当于{r,z}的二项集的次数为2,{r,t}二项集的次数为1。以此类推

那既然headerTable表中已经存放了生产二项集、三项集的次数,为什么还要建立对应的树呢?
又回到上文提到的对建树的理解。其实建树只是对原始数据的一种转换,把数据进行整合,有相同前缀的数据记录可以放到FP树的 一条分支上去。使得我们可以不用每次都在海量的原始数据中搜索,而是可以直接关注于与目标元素共现过的那些元素。

同样的,如果我们不建立模式基下的FP树,则我们在知道r的双项集{r,z},{r,x}…之后,我们需要找三项集的时候,还是需要对headerTable表中的所有元素进行两两组合。而有了模式基下的树后,我们可以快速找到哪些是与目标元素共现过的元素
比如对{r,z}找模式基发现z的前缀路径只有元素{x},于是可以很快得出在{r,z}出现的前提下,只有{r,z,x}, 而不会有{r,z,s}出现

所以再总结一下就是headerTable表用来计数,FP树用来找共现元素集合,以此类推不断找二项集、三项集等

最后提一点,书中所给出的代码中,只最后用列表保存了频繁项集,而没有给出对应频繁项集出现的次数。我也是根据我以上的理解,自己添加了代码增加了某一频繁项集的出现次数,代码如下:

def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
    bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p:p[1][0])]		#这里代码书中感觉也给错了
    print('bigL: ',bigL)
    print(inTree.printTree())
    for basePat in bigL:
        retDict = {}
        print('basePat: ', basePat)
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        print('newFreqSet: ', newFreqSet)
        retDict[frozenset(newFreqSet)] = headerTable[basePat][0]		#这里是添加项集出现次数的代码,以字典的形式保存
        #freqItemList.append(newFreqSet)
        freqItemList.append(retDict)
        condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
        print('condPattBases: ', condPattBases)
        myCondTree, myHead = createTree(condPattBases, minSup)

        print('head from conditional tree: ', myHead)
        if myHead != None:
            mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)

以上是我先学习FP-Growth算法过程中,产生的一些思考以及自己的理解。我一开始不太清除FP树的作用,书中讲解条件模式基和条件树的部分一开始也没有很好的理解清楚,就不断的自己根据思想,手动把树画出来然后一步一步对着算法流程进行分析。以上很多地方表达的不是特别清楚,也希望大家有什么不一样的想法都可以进行探讨讨论。感谢大家抽空看我吹了这么多的水,希望能起到一点帮助。

你可能感兴趣的:(ML,Algorithm)