【机器学习实战-12章】使用FP-growth算法来高效发现频繁项集

运行时错误:
Exception has occurred: RuntimeError
dictionary changed size during iteration
#dictionary changed size during iteration:遍历时不能修改字典元素
for k in headerTable.keys():
更改为 for k in list(headerTable.keys()):

12.0 概述

  1. 使用FP-growth算法来高效发现频繁项集
    使用搜索引擎时会自动补全。他们通过查看互联网上的用词来找出经常在一块出现的词对。这需要一种高效发现频繁集的方法。
  2. FP-growth算法基于Apriori算法构建,将数据集存储在一个特定的称作FP树的结构之后发现频繁项集或者频繁项对,即常在一块出现的元素项的集合FP树。
  3. FP-growth发现频繁项集的基本过程如下:
    1).构建FP树
    2).从FP树中挖掘频繁项集

12.1 FP树:用于编码数据集的有效方式

  1. FP-growth算法将数据存储在一种称为FP树的紧凑数据结构中。FP代表频繁模式(frequent pattern)。
    一棵FP树与其他的树结构类似,但是它通过链接(link)来连接相似元素,被连起来的元素项可以看成一个链表。
    在这里插入图片描述
    在这里插入图片描述

  2. 同搜索树不同的是,一个元素项可以在一棵FP树中出现多次。FP树回存储项集的出现频率,而每个项集会以路径的方式存储在树中。存在相似元素的集合会共享树的一部分。只有当集合之间完全不同时,树才会分叉。树节点上给出集合中的单个元素及其在序列中出现次数,路径会给出该序列的出现次数。

  3. 相似项之间的链接即节点链接(node link),用于快速发现相似项的位置。
    在图12-1中,元素项Z出现了5次,集合{r,z}出现了1次,于是可以得出结论:Z一定是子集本身或者和其他符号一起出现了4次(5-1).再看下Z的其他可能性。集合{t,s,x,z}出现了2次,集合{t,r,y,x,z}出现了1次。元素项Z的右边标的是5,表示Z出现了5次,其中刚才已经给出了4次出现,({t,s,x,z}:2;{t,r,y,x,z}:1;{r,z}:1),所以Z一定单独出现过1次。

在事务数据集中我们看到005号记录上{y,r,x,z,q,t,p},那么,q和p去哪儿了呢?
这里使用支持度定义,该指标对应一个最小阈值,低于最小阈值的元素项被认为是不频繁的。如果将最小支持度设为3,然后应用频繁项分析算法,就会获得出现3次或3次以上的项集。上述FP树设定最小支持度为3,故没有q和p.

  1. FP-growth算法的工作流程:首先构建FP树,然后利用它来挖掘频繁项集。
    为构建FP树,需要对原始数据集扫描两遍。第一遍对所有元素项的出现次数进行计数,统计频繁集出现的频率;第二遍扫描中只考虑那些频繁元素。

12.2 构建FP树

12.2.1 创建FP树的数据结构

  1. 创建一个类类保存树的每一个节点
#FP树的类定义
class treeNode(object):
    """保存FP树的每一个节点"""
    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)
if __name__ == '__main__':
    #创建根节点
    rootNode = treeNode('pyramid',9,None)
    #增加子节点
    rootNode.children['eye'] = treeNode('eye',13,rootNode)
    rootNode.children['ear'] = treeNode('ear',8,rootNode)
    #显示子节点
    rootNode.disp()

12.2.2 创建FP树

除了图12-1给出的FP树之外,还需要一个头指针表来指向给定类型的第一个示例。
这里使用一个字典作为数据结构,来保存头指针表。除了存放指针外,头指针表还可以用来保存FP树中每类元素的总数。
在这里插入图片描述
去掉不满足最小支持度的元素项。每个事务就是一个无序集合,在FP树种,相同项只表示一次,所以,在将集合添加到树之前,需要对每个集合进行排序。排序基于元素项的绝对出现频率来进行。
在这里插入图片描述
在对事务记录过滤和排序之后,就可以构建FP树了。从空集开始,向其中不断添加频繁项集。过滤、排序后的事务依次添加到树中,如果树中以存在现有元素,则增加现有元素的值;如果元素不存在,则向树添加一个分支。
在这里插入图片描述

def createTree(dataSet,minSup=1):
    """树构建过程种会遍历数据集两次。
    第一遍遍历扫描数据集并统计每个元素项出现的频度,这些信息被存储在头指针表中
    第二遍,考虑频繁项集"""
    headerTable = {}    #头指针表
    #第一遍遍历扫描数据集并统计每个元素项出现的频度
    for trans in dataSet:
        for item in trans:
            print(headerTable.get(item,0))
            headerTable[item] = headerTable.get(item,0) + dataSet[trans]
            print(dataSet[trans])
            print(headerTable.get(item,0) + dataSet[trans])
            print(headerTable)
    #for k in headerTable.keys():    #dictionary changed size during iteration:遍历时不能修改字典元素
    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):
    """growth生长FP-growth"""
    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):
    """确保节点链接指向数中该元素项的每一个实例
    从头指针表的nodeLink开始,一直沿着nodeLink直到到达链表末尾"""
    while(nodeToTest.nodeLink != None):
        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
def createInitSet(dataSet):
    """将原始数据集从列表转换为字典格式"""
    retDict = {}
    for trans in dataSet:
        retDict[frozenset(trans)] = 1
    return retDict

if __name__ == '__main__':
     #简单数据测试
    simpDat = loadSimpDat()
    initSet = createInitSet(simpDat)
    print(initSet)
    myFPtree,myHeaderTab = createTree(initSet,3)
    myFPtree.disp()

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

思路与Apriori算法大致类似。
1)从FP树中获得条件模式基
2)利用条件模式基,构建一个条件FP树
3)迭代重复前两个步骤,直到树包含一个元素项为止

12.3.1 抽取条件模式基

  1. 首先从已经保存在头指针表中的单个频繁元素项开始。对于每一个元素项,获得其对应的条件模式基(conditional pattern base).条件模式基是以所查找元素项为结尾的路径集合。每一条路径其实都是一条前缀路径(prefix path).简而言之,一条前缀路径是介于所查找元素项与树根节点之间的所有内容
  2. 图12-2中,符号r的前缀路径是{x,s},{z,x,y}和{z}。每一条前缀路径都与一个计数值关联,该计数值等于起始元素项的计数值,该计数值给了每条路径上r的数目。
  3. 可以利用头指针来进行前缀路径的创建。头指针表包含相同类型元素链表的起始指针。一旦到达了每一个元素项,就可以上溯这棵树直到根节点为止。
    在这里插入图片描述
#发现以给定元素项结尾的所有路径的函数
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
if __name__ == '__main__':
    simpDat = loadSimpDat()
    initSet = createInitSet(simpDat)
    myFPtree,myHeaderTab = createTree(initSet,3)
	condPats = findPrefixPath('x',myHeaderTab['x'][1])
    print(condPats)	#{frozenset({'z'}): 3}

12.3.2 创建条件FP树

对于每一个频繁项,都要创建一棵条件FP树。我们会为z,x以及其他频繁项构建条件数。可以使用刚才发现的条件模式基作为输入数据,并通过相同的建树代码来构建这些树。然后,我们会递归地发现频繁项、发现条件模式基,以及发现另外的条件树。元素项 t 的条件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:
        #将每一个频繁项添加到频繁项集列表freqItemList中
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        freqItemList.append(newFreqSet)
        #递归调用findPrefixPath()函数来创建条件基
        condPattBases = findPrefixPath(basePat,headerTable[basePat][1])
        #从条件模式基来构建条件FP树
        myCondTree,myHead = createTree(condPattBases,minSup)
        if myHead != None:
            #挖掘条件FP树:如果树中有元素项的话,递归调用minTree()函数
            print('conditional tree for:',newFreqSet)
            myCondTree.disp()
            mineTree(myCondTree,myHead,minSup,newFreqSet,freqItemList)

12.4 本章小结

FP-growth算法是一种用于发现数据集中频繁模式的有效方法。FP-growth算法利用Apriori原则,执行更快。在FP-growth算法中,数据集存储在一个称为FP树的结构中。FP树构建完成后,可以通过查找元素项的条件基及构建条件FP树来发现频繁项集。该过程不断以更多元素作为条件重复进行,直到FP树只包含一个元素为止。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值