运行时错误:
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 概述
- 使用FP-growth算法来高效发现频繁项集
使用搜索引擎时会自动补全。他们通过查看互联网上的用词来找出经常在一块出现的词对。这需要一种高效发现频繁集的方法。 - FP-growth算法基于Apriori算法构建,将数据集存储在一个特定的称作FP树的结构之后发现频繁项集或者频繁项对,即常在一块出现的元素项的集合FP树。
- FP-growth发现频繁项集的基本过程如下:
1).构建FP树
2).从FP树中挖掘频繁项集
12.1 FP树:用于编码数据集的有效方式
-
FP-growth算法将数据存储在一种称为FP树的紧凑数据结构中。FP代表频繁模式(frequent pattern)。
一棵FP树与其他的树结构类似,但是它通过链接(link)来连接相似元素,被连起来的元素项可以看成一个链表。
-
同搜索树不同的是,一个元素项可以在一棵FP树中出现多次。FP树回存储项集的出现频率,而每个项集会以路径的方式存储在树中。存在相似元素的集合会共享树的一部分。只有当集合之间完全不同时,树才会分叉。树节点上给出集合中的单个元素及其在序列中出现次数,路径会给出该序列的出现次数。
-
相似项之间的链接即节点链接(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.
- FP-growth算法的工作流程:首先构建FP树,然后利用它来挖掘频繁项集。
为构建FP树,需要对原始数据集扫描两遍。第一遍对所有元素项的出现次数进行计数,统计频繁集出现的频率;第二遍扫描中只考虑那些频繁元素。
12.2 构建FP树
12.2.1 创建FP树的数据结构
- 创建一个类类保存树的每一个节点
#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 抽取条件模式基
- 首先从已经保存在头指针表中的单个频繁元素项开始。对于每一个元素项,获得其对应的条件模式基(conditional pattern base).条件模式基是以所查找元素项为结尾的路径集合。每一条路径其实都是一条前缀路径(prefix path).简而言之,一条前缀路径是介于所查找元素项与树根节点之间的所有内容。
- 图12-2中,符号r的前缀路径是{x,s},{z,x,y}和{z}。每一条前缀路径都与一个计数值关联,该计数值等于起始元素项的计数值,该计数值给了每条路径上r的数目。
- 可以利用头指针来进行前缀路径的创建。头指针表包含相同类型元素链表的起始指针。一旦到达了每一个元素项,就可以上溯这棵树直到根节点为止。
#发现以给定元素项结尾的所有路径的函数
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树只包含一个元素为止。