使用FP-growth算法高效发现频繁项集

FP-growth算法:将数据集存储在一个特定的称为FP树的结构之后发现频繁项集或者频繁项对,即常在一起出现的元素项的集合FP树。

工作流程:

1、构建FP树:需要扫描两遍数据集,第一遍对所有元素项的出现次数进行计数,第二遍扫描时只关注频度满足要求的元素项。

2、抽取条件模式基

3、创建条件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树中只有一条路径来表示这种元素项集,例如元素项集(a,b,c,d)和(d,c,b,a)在FP树中只能对应同一条路径。所以在将集合(元素项集)添加到树之前,需要对集合里的元素排序。排序是基于元素的绝对出现频度来进行的。

在对事务记录过滤和排序之后,构建FP树:从空集开始,向其中依次添加频繁项集。如果树中已经存在现有元素,则增加现有元素的值;如果现有元素不存在,则向树添加一个分枝。

FP树构建函数:

伪代码:

输入:数据集、最小频度
输出:FP树、头指针表
1. 遍历数据集,统计各元素项出现次数,创建头指针表
2. 移除头指针表中不满足最小值尺度的元素项
3. 第二次遍历数据集,创建FP树。对每个数据集中的项集:
    3.1 初始化空FP树
    3.2 对每个项集进行过滤和重排序
    3.3 使用这个项集更新FP树,从FP树的根节点开始:
        3.3.1 如果当前项集的第一个元素项存在于FP树当前节点的子节点中,则更新这个子节点的计数值
        3.3.2 否则,创建新的子节点,更新头指针表
        3.3.3 对当前项集的其余元素项和当前元素项的对应子节点递归3.3的过程

 代码

def createTree(dataSet, minSup=1):
    # 第一次遍历数据集,创建头指针表
    headerTable = {}
    for trans in dataSet:
        for item in trans:
            headerTable[item] = headerTable.get(item, 0) + dataSet[trans]
    # 移除不满足最小支持度的元素项
    headerTableCopy=headerTable.copy()  #浅拷贝,拷贝第一层
    for k in headerTableCopy.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) # 根节点
    # 第二次遍历数据集,创建FP树
    for tranSet, count in dataSet.items():
        localD = {} # 对一个项集tranSet,记录其中每个元素项的全局频率,用于排序(基于绝对出现频率排序)
        for item in tranSet:
            if item in freqItemSet:
                localD[item] = headerTable[item][0]     #[0]表示对应元素的频度,[1]表示指向的节点
        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) # 更新FP树
    return retTree, headerTable

def updateTree(items, inTree, headerTable, count):  #元素项集,FP树,头指针表,事务的出现次数
    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函数
        updateTree(items[1::], inTree.children[items[0]], headerTable, count)
def updateHeader(nodeToTest, targetNode):   #改变当前节点的指向节点为targetNode
    while (nodeToTest.nodeLink != None):
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode

从一棵FP树中挖掘频繁项集

从FP树中获得频繁项集的三个基本步骤:

1、从FP树中获得条件模式基

2、利用条件模式基,构建一个条件FP树

3、迭代步骤1和2,直到树只包含一个元素为止

条件模式基:是以所要查找的元素为结尾的路径集合。每一条路径其实都是一条前缀路径。简而言之,一条前缀路径是介于所查找元素与树根节点之间的所有内容。

前缀路径用于构建条件FP树。前缀路径的实现方法:头指针表包含相同类型元素链表的起始指针。一旦到达了每一个元素项,就可以上溯这棵树直到根节点为止。

前缀路径发现代码:

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
def ascendTree(leafNode, prefixPath):
    if leafNode.parent != None:
        prefixPath.append(leafNode.name)
        ascendTree(leafNode.parent, prefixPath)

 创建条件FP树:在递归创建FP树的过程中会找到频繁项集

伪代码:

输入:我们有当前数据集的FP树(inTree,headerTable)
1. 初始化一个空列表preFix表示前缀
2. 初始化一个空列表freqItemList接收生成的频繁项集(作为输出)
3. 对headerTable中的每个元素basePat(按计数值由小到大),递归:
        3.1 记basePat + preFix为当前频繁项集newFreqSet
        3.2 将newFreqSet添加到freqItemList中
        3.3 计算t的条件FP树(myCondTree、myHead)
        3.4 当条件FP树不为空时,继续下一步;否则退出递归
        3.4 以myCondTree、myHead为新的输入,以newFreqSet为新的preFix,外加freqItemList,递归这个过程

 代码:

def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
    # for i in headerTable.items():
    #     print(i[1])
    # print('********************************')
    bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p:str(p[1]))]    #因为p[1]中第一个元素是int,第二个是treeNode对象,当第一个相等时会比较第二个
    for basePat in bigL:
        #print('basePat:',basePat)
        #print('preFix:',preFix)
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        freqItemList.append(newFreqSet)
        condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
        #print('条件模式基:',condPattBases)
        myCondTree, myHead = createTree(condPattBases, minSup)
        print('myHead:',myHead)
        if myHead != None:
            # 用于测试
            print('基于%s的条件树:'%newFreqSet)
            myCondTree.disp()
            mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)

 辅助函数:原始数据集是字典,键是事务(记录),值是此记录出现的次数

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

 测试:

if __name__=='__main__':
    simpData = loadSimpDat()
    initSet = createInitSet(simpData)
    myFPtree, myHeaderTab = createTree(initSet, 3)
    freqItems = []
    mineTree(myFPtree, myHeaderTab, 3, set([]), freqItems)
    print(freqItems)

输出:

基于{'s'}的条件树:
  Null Set   1
   x   3
基于{'y'}的条件树:
  Null Set   1
   x   3
    z   3
基于{'y', 'z'}的条件树:
  Null Set   1
   x   3
基于{'t'}的条件树:
  Null Set   1
   y   3
    x   3
     z   3
基于{'z', 't'}的条件树:
  Null Set   1
   x   3
    y   3
基于{'y', 'z', 't'}的条件树:
  Null Set   1
   x   3
基于{'x', 't'}的条件树:
  Null Set   1
   y   3
基于{'x'}的条件树:
  Null Set   1
   z   3
[{'r'}, {'s'}, {'x', 's'}, {'y'}, {'y', 'z'}, {'y', 'x', 'z'}, {'y', 'x'}, {'t'}, {'z', 't'}, {'x', 'z', 't'}, {'y', 'z', 't'}, {'y', 'x', 'z', 't'}, {'y', 't'}, {'x', 't'}, {'x', 't', 'y'}, {'x'}, {'x', 'z'}, {'z'}]

递归过程:

 参考

 总结:FP-growth算法是一种用于发现数据集中频繁模式的有效方法。FP-growth算法利用Apriori原则,执行更快。Apriori算法用来产生候选项集,然后扫描数据集来检查它们是否频繁。由于只对数据集扫描两次,因此FP-growth算法执行更快。在FP-growth算法中,数据集存储在一个称为FP树的结构中。FP树构建完成后,可以通过查找元素项的条件基构建条件FP树来发现频繁项集。该过程不断以更多元素作为条件重复进行,直到FP树只包含一个元素为止。

使用FP树来存储数据,包括每个候选频繁元素的链接,后续使用这些链接来寻找前缀路径,在用前缀路径在生成条件FP树来找到频繁项集。

由原始数据集得来的FP树包含两方面的重要信息:每个元素的链接,用来迅速查找元素的所有前缀路径;每个元素所在的路径,用来表明元素和其他哪些元素同时出现。

原文地址:https://www.cnblogs.com/zhhy236400/p/9992227.html