FP-growth算法

对于发现频繁项集,Apriori是一个很好的算法,但Apriori在发现频繁项集的时候需要多次扫描数据库,这严重影响了速度。

而FP-growth算法基于Apriori构建,不过在完成相同的发现频繁集的任务上,它采用了一些不同的技术。将数据集存储在一个特定的被称为FP树的结构之后去发现频繁项集。这种做法使得其只需对数据库进行两次扫描,从而大大提高了其发现频繁项集的速度。

FP-growth发现频繁项集的基本过程如下:

  1. 构建FP树
  2. 从FP树中挖掘频繁项集

    图 1 FP树是以下面的数据集构造成的

    图2 数据集

    要构造FP树,首先需要定义FP树的节点,

    # fp树节点结构

    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)

    为构建FP树,会扫描两遍数据库。

    第一遍,对所有元素项出现的次数进行计数,去掉不满足最小支持度的元素项。

    第二遍,构建FP树,FP树会存储项集的出现频率,而每个项集会以路径的方式存储在树中,存在相似元素的集合会共享树的一部分。只有当集合之间完全不相同时,树才会分叉。

    为了构造FP树和挖掘频繁项集,还需要构造一个头指针表来辅助。

    图3 带有头指针表的FP树

    FP树的构造和构造辅助的头指针表实现如下

    # FP树的构造

    def createTree(dataSet,minSup=1):

    headerTable={} # 头指针表

    for trans in dataSet:

    for item in trans:

    headerTable[item]=headerTable.get(item,0)+dataSet[trans]

    # 移除不满足最小支持度的元素项

    for k in list(headerTable.keys()):

    if headerTable[k]<minSup:

    del(headerTable[k])

    # 根据头指针表 建频繁元素项集

    freqItemSet =headerTable.keys()

    if len(freqItemSet)==0: return None,None

     

    for k in headerTable:

    headerTable[k]=[headerTable[k],None]

    # 建立根节点

    retTree = treeNode('Null Set',1,None)

    # 第二次扫描数据集,建立 FP 树, 并对事务中的频繁元素排序

    for tranSet,count in dataSet.items():

    localD={}

    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

     

    def updateTree(items,inTree,headerTable,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

    用图2 的数据集测试构造FP树

    图4 测试构造FP树

    有了FP树之后,就可以抽取频繁项集了。其思路大致和Apiori相似,首先从单个元素项开始,然后在此基础上逐步构建更大的集合。基本步骤如下:

  3. 从FP树中获得条件基模式
  4. 利用条件基模式,构建一个条件FP树(条件基模式,即以所查找的元素项为结尾,然后上溯FP树,找出以该元素为结尾的所有前缀路径的集合)
  5. 迭代重复步骤1和2,直到树包含一个项集为止

    # 挖掘频繁项集

    def mineTree(inTree,headerTable,minSup,preFix,freqItems):

    bigL = [v[0] for v in sorted(headerTable.items(),key=lambda p :p[0])]

    for basePat in bigL:

    newFreqSet=preFix.copy()

    newFreqSet.add(basePat)

     

    # freqItems.append(newFreqSet)

    # freqItemSet 修改为字典结构,便于规则的发现生成

    tmp=newFreqSet.copy()

    tmp=frozenset(tmp)

    freqItems[tmp]=headerTable[basePat][0]

     

    conPatBases=findPrefixPath(basePat,headerTable[basePat][1])

    myCondTree,myHead=createTree(conPatBases,minSup)

    if myHead!=None:

    mineTree(myCondTree,myHead,minSup,newFreqSet,freqItems)

     

    # 上溯整棵树

    def ascendTree(leafNode,prefixPath):

    if leafNode.parent!=None:

    prefixPath.append(leafNode.name)

    ascendTree(leafNode.parent,prefixPath)

    # 找出前缀路径

    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树挖掘出所有频繁项集

    图5 挖掘出数据集的频繁项集

    最后是利用所发现的频繁项集,发现生成出满足一定置信度的规则

    # 从频繁项集中找出符合最小置信度的规则

    def disp_rules(freqItems,minconf):

    n_rules=0

    for item1,value1 in freqItems.items():

    for item2,value2 in freqItems.items():

    conf=value2/float(value1)

    if (item1 != item2) and (item1.issubset(item2)) and conf>minconf:

    print(item1,' --> ',(item2-item1),' conf:',conf)

    n_rules+=1

    print('the number of total rules :',n_rules)

    disp_rules(freqItems,minconf=0.7)

    图6 根据频繁项集发现生成的规则

posted @ 2020-05-03 16:07  lincoding`  阅读(487)  评论(0编辑  收藏  举报