机器学习之九——FP-growth算法

FP-growth算法跟apriori算法一样,都是用来发现频繁集和关联规则的,不过由于FP-growth算法用了树结构,效率比apriori快了很多。FP-growth算法中选取频繁集时,只需要对整个数据集扫描两次,而apriori算法对于每个候选频繁集就会对数据集进行扫描,可见FP-growth算法的效率之高。下面,博主就为大家介绍一下FP-growth算法。

一、FP树结构

FP-growth算法中的树称为FP树,那么FP树的结构是怎样的呢?我们用下面的例子来说明,提前声明一下,以下的例子均来自于《机器学习实战》一书。

上图是6个事务,可以简单地理解为6条交易,最后一列是频繁的元素,我们将用最后一列来构建FP树,如下图:

可以看到, 这个FP树分为两个部分。右边的树状图暗含了每条交易记录。这棵树的建立方式如下:

1. 对于001号交易记录{z,r},从根节点开始,观察根节点的子节点中是否有z这个节点,如果没有就创建一个,然后给z赋值为1,代表z出现的次数;然后再观察以z为根节点的子节点是否有r这个节点,如果没有就创建一个,然后给r赋值为1,代表r出现的次数

2. 对于002号交易记录{z,x,y,s,t},从根节点开始,观察根节点的子节点中是否有z这个节点,发现z已经存在,那么z的值加1,代表z出现了两次;然后再观察以z为根节点的子节点中是否有x这个节点,没有则创建一个,然后给x赋值为1,对于y,s,t依次进行

3. 对于003号交易记录{z},从根节点开始,观察根节点的子节点中是否有z这个节点,发现z已经存在,那么z的值加1

4. 对于004号交易记录{x,s,r},从根节点开始,观察根节点的子节点中是否有x这个节点,如果就创建一个并赋值为1,然后对s和r依次进行

5. 对于005号交易记录{z,x,y,r,t},在搜索树的过程中发现已经存在z,x,y的路径,y的子节点下没有r,于是在y的子节点中构建一个名字为r的节点,在r的子节点中构建一个名字为t的节点

6. 对于006号交易记录{z,x,y,s,t},在搜索树的过程中发现已经存在这条路径,则只会改变值不会改变树的结构。

以上就是树的构建过程,现在来讲解左边的头指针表。

头指针表的记录了每个频繁元素的名字以及其出现的总次数,其指针指向相同名字的元素,用来更快的搜索相同名字元素。

二、FP树的构建

接下来就讲解一下FP树的代码。

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

def createInitSet(dataSet):
    retDict = {}
    for trans in dataSet:
        retDict[frozenset(trans)] = 1
    return retDict

上面两个函数是数据预处理过程,处理的结果是将所有的交易记录存放在字典中,字典的键是交易记录的frozenset形式,值为1。

下面的代码定义了一个树的数据结构:

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)

其中disp()函数是用来以文本形式展示树,如下图:

缩进就代表着子树。

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 = set(headerTable.keys())
    if len(freqItemSet) == 0:
        return None,None
    for k in headerTable:
        headerTable[k] = [headerTable[k],None]  #第二个元素用来连接同名节点
    retTree = treeNode('Null Set',1,None)
    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)
        #FP树中添加了节点,因此要判断头节点表是否需要更新
        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

上面的是创建树的代码,第一次扫描的过程中删除了非频繁元素,第二次扫描则用频繁元素创建了树。回过头去看上面介绍的树的创建过程会发现,每条交易记录中元素的顺序显然会影响树的形状,因此我们需要对元素进行排序,排序的依据就是它们出现的次数,第二次扫描中的orderedItems即用来保存排序后的元素排列,然后使用orderedItems来更新树。

在树的更新过程中,首先判断当前树的子节点中是否有第一个元素,如果有,让其加1就好了;如果没有,就创建一个以该元素为根节点的子树,此时头指针表也需要更新。如果头指针表不存在连接,就让其连接到该元素;如果存在连接,就让该连接中的最后一个节点再连接到这第一个元素。

运行上一段代码会得到下面这样的结果:

三、条件模式基

有了FP树,现在我们就要从中挖掘频繁项集了。在挖掘之前,先介绍一下条件模式基的概念。条件模式基是以所查按照元素项为结尾的路径,而介于所查找元素与树根节点之间的路径称为前缀路径(prefix path)。抽取条件模式基的代码如下:

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 condPat

运行结果如图:

四、创建条件FP树

FP-growth算法中抽取频繁集的思路跟apriori算法一样,都是从单元素项集开始,然后逐步构建更大的集合。而一个频繁集,在这里用一个树来表示,称作条件FP树。条件FP树用条件模式基作为输入数据,然后递归的发现频繁项、发现条件模式基,以及发现另外的条件树。代码如下:

def mineTree(inTree,headerTable,minSup,preFix,freqItemList):
    bigL = [v[0] for v in sorted(headerTable.items(),key=lambda p:p[1][0])]
    for basePat in bigL:
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        freqItemList.append(newFreqSet)
        condPattBases = findPrefixPath(basePat,headerTable[basePat][1])
        myCondTree,myHead = createTree(condPattBases,minSup)
        if myHead != None:
            mineTree(myCondTree,myHead,minSup,newFreqSet,freqItemList)

运行后输出如下:

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值