FP-Growth算法理解

第一次接触FP-Growth是在《数据挖掘概念与技术》,当时对它的理解只停留在概念层面。后来又在《机器学习实战》中接触到了它,结合着书中的讲解和代码,跑了点结果,理解加深了一点。最近,工作中需要使用到它,又重新捡起,开始精读和思考,发现收获很大。

FP-Growth(Frequent Pattern Growth, 频繁模式增长),它比Apriori算法效率更高,在整个算法执行过程中,只需要遍历数据集2次,就可完成频繁模式的发现。

大白话描述FP-Growth:

1 输入

1.1 数据集dataset:假设有N条事务,每个事务是包含元素个数不定的集合,如

dataset=[['啤酒','牛奶','可乐'],
         ['尿不湿','啤酒','牛奶','橙汁'],
         ['啤酒','尿不湿'],
         ['啤酒','可乐','尿不湿']]

1.2 支持度sup

2 计算过程:

2.1 创建FP-tree

对于输入的dataset,统计所有事项中各元素的出现频次,即每个1项集的频数,并将各元素按照频数降序排序,删除那些出现频数少于设定支持度sup的元素,形成列表L,留下来的元素就构成了频繁1项集。(这是对数据集的第一遍扫描)

对数据集中每个事务的元素按照列表L排序(按支持度降序排列),开始构造FP-tree。树的根节点为空,每个事务中的所有元素形成一条从根节点到叶子结点的路径。若几个事务的元素按列表L排序后,具有相同的前m个元素,则它们在FP-tree中共享前m个元素代表的节点。树中每个节点的计数为路径经过该节点的事务集的个数。(这是对数据集的第二遍扫描)

在创建FP-tree的同时,headTable也就创建好了,headTable可以理解为一个具有三列的表。第一列为元素(项ID),第二列为支持度计数,第三列为节点链。如下所示

项ID 支持度计数 节点链
啤酒 4 nodelink1
尿不湿 3 nodelink2
牛奶 2 nodelinkn

headTable中的项也是按照支持度计数从大到小排列的。节点链则链接到FP-tree中这一项所在的各个叶子结点上,后面频繁项集的发现就是靠的这些节点链。

2.2 寻找FP

从headTable中的最后一行开始,依次往上取项,比如最开始我们取‘牛奶’。寻找‘牛奶’的所有前缀路径,这些前缀路径形成‘牛奶’的CPB(Condition Pattern Base,条件模式基),而这些CPB又形成新的事务数据库。将‘牛奶’这一项添加到我们的集合中,此时,‘牛奶’这个频繁1-项集的支持度就是headTable中的支持度计数。然后用‘牛奶’的CPB形成的事务数据构造FP-tree,如2.1所述,构造的过程中将不满足支持度的项删除,而满足支持度的项又会构成另外一个FP-tree和headTable,我们记为FP-tree1和headTable1。同样的从headTable1的最后一行开始,比如是可乐,那么把‘可乐’和之前的‘牛奶’就形成了频繁2-项集,此时,{‘牛奶’,‘可乐’}这个频繁2-项集的支持度就是headTable1中‘可乐’的支持度。同时,我们也要寻找‘可乐’的前缀路径形成的CPB构成的又一个事务数据集,仍旧是构造FP-tree,从headTable取元素项,将元素项加集合。。。

不知大家发现没,FP的发现过程就是一个循环里不断递归的操作。循环,循环的是headTable中的各个元素项;递归,递归的是元素项的CPB构成的事务数据集形成的FP-tree中发现FP。

实践出真知

起初,我用《机器学习实战》中的代码,稍加修改,得到了频繁项集和关联规则,但由于数据量大,程序跑起来有点慢,就往Spark上移植(Spark的MLlib库中有现成的FP-Growth接口)。就在我移植完,跑了一遍后,尴尬的事情发生了,两种实现跑出来的结果不一样,而且差别还挺大的!经过自己确认,两种实现输入都是一样的,Spark上直接调用的接口,输入格式也是它要求的格式。于是就把目光转到了FP-Growth算法原理和《机器学习实战》中的代码实现上。

在网上看了很多博主对FP-Growth算法的解读,最后看到这篇时,http://www.cnblogs.com/qwertWZ/p/4510857.html  发现了一些端倪。

这篇博文最后提到,《机器学习实战》中的createInitSet函数在统计事务时,简单的将频数都置为1,如原函数所示。对于没有重复事务的数据集,这种方法并没有什么问题,但是当数据集中存在相同事务时,这种频数置1的做法就会带来错误,正确的做法是应当对相同的事务频数累加起来,如修改后的函数所示。

原函数:

def createInitSet(dataSet):
    retDict = {}
    for trans in dataSet:
        retDict[frozenset(trans)] = 1#如有相同事项,则计数出错
    return retDict
修改后的函数:

def createInitSet(dataSet):
    retDict = {}
    for trans in dataSet:
        retDict[frozenset(trans)] = retDict.get([frozenset(trans)], 0) + 1#若没有相同事项,则为1;若有相同事项,则加1
    return retDict

将这一行代码修改后,再运行,两种实现的结果就一样了。

完整的fp-growth及关联规则代码在这里

FP-Growth的python包

由于我之前用sklearn比较多,但要实现频繁项集挖掘时,发现sklearn并没有包含apriori和fp-growth这类频繁项集发现算法,于是就直接借用《机器学习实战》里的代码了。后来在解决问题的过程中发现有一个名叫fp_growth包,安装后,发现调用非常简单。

import fp_growth as fpg
frequent_itemsets = fpg.find_frequent_itemsets(transactions, minimum_support, include_support=False)
其中,transactions是我们的输入,格式可以是list of list;minimum_support是算法的最小支持度,这个是小数表示的;include_support设置为True,则结果中包括频繁项集的支持度,否则不包括;frequent_itemsets就是我们要获得的频繁项集。



发布了25 篇原创文章 · 获赞 14 · 访问量 5万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章