机器学习之频繁模式树(一)

上一篇博客介绍了Apriori算法,该算法有一个致命的缺陷,就是在产生频繁模式完全集前需要对数据库进行多次扫描,同时产生大量的候选频繁集,这就使得Apriori算法时间和空间复杂度过大。基于上述原因,韩家炜等人提出了FP-tree算法,该算法仅需要扫描两次数据库,就可以完成Apriori算法的功能。在算法中使用了特殊的数据结构频繁模式树(Frequent Pattern Tree),由频繁项头表和项前缀树构成(这里也用到了Apriori的一些性质,比如:集合A非频繁,那么包含结合A的所有集合都是非频繁,如果不知道,请看上一篇博客的推导过程,这里不在叙述了。。)。这里先举一个例子说明:

机器学习之频繁模式树(一)_第1张图片
如上图所示,就是一个FP-Tree,看起来貌似和我们以前数据结构学习的树不大一样,多了一个频繁项头表(Header Table),那么他是如何构建树的哪?
第一步:扫描数据库,找出并记录大于最小支持度的所有事物项,并且初始化频繁项头表。
第二步:扫描数据库,对数据集中每一个事物按照事物项的频次的大小降序排列。在对该事物中的每一个事物项做如下操作:
1,树更新:如果根节点已包含该事物的第一个事物项,则事物项的值+1,否则新建一个节点。
2,频繁项头表更新:如果频繁项头表已经包含该事物项,则找出链表的末尾位置,加入该项;否则,直接加入
3,重复1,2两步操作,知道该事物遍历完成,接着进行下一个事物同样的操作。。
确实不太好用语言来描述,但是如果根据图和文字理解,就会明白是什么意思,FP-tree不仅要描述一个事务中不同事务项之间的联系(一个行数据中事务项的联系),还要描述所有事物中,同类型事务项的联系(比如:所有名称为x的事务项的联系是一个链表,x:4->x:3->x:1)。下图是一个简单的构建过程:机器学习之频繁模式树(一)_第2张图片
上图中首先对原始数据进行频繁集过滤,然后按照支持度降序排列(这个很重要),{z,r}是最先加入到根节点当中,当{z,x,y,s,t}再加入时,z可以实现两颗子树的公用,因此z节点的值记为2,依次类推直到树的结构建成为止。
下面看看原有的代码是如何实现的,说实话我看了好多遍才恍然大悟(代码还是要认真的去读的,实现的代码很多种,这里还是Machine in Action中的代码)。`”’
Created on Jun 14, 2011
FP-Growth FP means frequent pattern
the FP-Growth algorithm needs:
1. FP-tree (class treeNode)
2. header table (use dict)

This finds frequent itemsets similar to apriori but does not
find association rules.

''' Created on Jun 14, 2011 FP-Growth FP means frequent pattern the FP-Growth algorithm needs: 1. FP-tree (class treeNode) 2. header table (use dict) This finds frequent itemsets similar to apriori but does not find association rules. @author: Peter '''
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)

def createTree(dataSet, minSup=1): #create FP-tree from dataset but don't mine
    headerTable = {}#初始化频繁项头表
    #go over dataSet twice
    for trans in dataSet:#first pass counts frequency of occurance
        for item in trans:
            headerTable[item] = headerTable.get(item, 0) + dataSet[trans]#第一遍扫描数据集,并且记录每一个事物项的频次
    for k in headerTable.keys():  #去除频次小于门限值的事物项
        if headerTable[k] < minSup: 
            del(headerTable[k])
    freqItemSet = set(headerTable.keys())#频繁事物集
    #print 'freqItemSet: ',freqItemSet
    if len(freqItemSet) == 0: return None, None  #if no items meet min support -->get out
    for k in headerTable:
        headerTable[k] = [headerTable[k], None] #频繁项头表 
    #print 'headerTable: ',headerTable
    retTree = treeNode('Null Set', 1, None) #create tree
    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)]#根据key的关键字排序
            updateTree(orderedItems, retTree, headerTable, count)#
#更新树和表
    return retTree, headerTable #return tree and header table

def updateTree(items, inTree, headerTable, count):
    if items[0] in inTree.children:#如果节点的子节点已经包含该事物,则记数增加1
        inTree.children[items[0]].inc(count) #incrament 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:#call updateTree() with remaining ordered items
        updateTree(items[1::], inTree.children[items[0]], headerTable, count)#继续对下一个频繁事物项操作

def updateHeader(nodeToTest, targetNode):   #this version does not use recursion
    while (nodeToTest.nodeLink != None):    #Do not use recursion to traverse a linked list!
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode
    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
minSup = 3
simpDat = loadSimpDat()
initSet = createInitSet(simpDat)
myFPtree, myHeaderTab = createTree(initSet, minSup)
myFPtree.disp()
myFreqList = []
mineTree(myFPtree, myHeaderTab, minSup, set([]), myFreqList)

你可能感兴趣的:(python,机器学习,FP-Tree,频繁模式树)