FP树
FP-growth算法将数据存储在一种称为FP树的紧凑数据结构中。FP代表频繁模式。一棵FP树看上去和其他的树类似,但是它通过链接来连接相似的元素,被连接的元素可以看作一个链表。下面是FP树的一个例子:
同搜索树不同,一个元素可以在FP树中出现多次,FP树会存储项集出现的频率,每个项集会以路径形式存在树中。存在相似元素的集合会共享树的一部分。树节点给出集合中单个元素及其在序列中出现的次数,路径会给出该序列出现的次数。相似项之间的链接即为节点连接,用于快速发现相似项的位置。链接在图中用虚曲线表示,它链接了相同的元素。
下面是构成这个FP树所用到的数据:
ID | 元素项 |
---|---|
1 | rzhjp |
2 | zyxwvuts |
3 | z |
4 | rxnos |
5 | yrxzqtp |
6 | yzxeqstm |
在上面的那棵树中,项集中的元素是某个节点到根节点之间路径上所有元素的集合,该项集出现的频率是第一个节点旁边代表的数字。比如集合{z}一共出现了5次,集合{r,z}一共出现了一次,这和表中的数据结论是相吻合的。至于树中没有出现的元素,是因为我们同样对这棵树设置了最小支持度,小于该值的项集不会出现。这棵树设置的最小支持度为3,所以其他的元素并没有出现在这棵树上。
FP-growth算法的工作流程如下:首先构成FP树,然后利用它来挖掘频繁项集。
为了构建FP树,需要对原始数据扫描两遍。第一遍对所有元素项出现的次数进行统计,把那些不符合支持度的元素去掉。数据库的第一遍扫描用于统计出现的频率,第二次扫描只考虑那些频繁元素。
构建FP树
因为最终结果是要构建一棵FP树,为成功构建该树,需要一个容器来保存。
创建FP树的数据结构
由于树中需要保存大量信息,为了方便之后的计算,我们将树节点存储到一个类中。
class TreeNode:
def __init__(self,nameVal,numCnt,parentNode):
self.name=nameVal #元素名
self.cnt=numCnt #出现次数
self.nodeLink=None #节点链接
self.parent=parentNode #因为路径是从下到上遍历节点,所以需要父节点
self.children={
} #孩子字典
def inc(self,numCnt): #自增运算
self.cnt+=numCnt
def display(self,ind=1): #结构展示
print(' '*ind,self.name,' ',self.cnt)
for child in self.children.values():
child.display(ind+1)
现在的FP树数据结构已经构建好了,下面就可以构建FP树了
构建FP树
除了构建FP树所需的数据结构,还需要一个头指针来指向给定类型的第一个实例。利用头指针,可以快速访问树中给定类型的所有元素,下面是一个头指针表的示意图:
这里的头指针表用字典的形式来存储。除了用于保存指针外,头节点还用来保存FP树种每类元素d总数。
第一次遍历数据集会获得每个元素项出现的频率。接下来去掉不满足最小支持度的元素项。再下一步构建FP树。在构建时,读入每个项集并将其添加到已经存在的路径中,如果路径不存在,则创建一条新路径。
每个事务是一个无序集合,假设有集合{z,y}和{y,z},那么在FP树中,相同项只会表示一次。为了解决这个问题,将集合添加到树之前,需要对每个集合进行排序。排序基于元素项的出现频率来进行。上面的数据经过去除非频繁项集和排序后得到以下结果:
ID | 元素项 | 处理后 |
---|---|---|
1 | rzhjp | zr |
2 | zyxwvuts | zxyst |
3 | z | z |
4 | rxnxsros | xsr |
5 | yrxzqtp | zxyrt |
6 | yzxeqstm | zxyst |
在对集合进行过滤排序之后,就可以构建FP树了。从空集开始,向其不断添加频繁项集。过滤排序后的事务依次添加到树中,如果树中已存在现有的元素,则增加现有元素的值。如果元素不存在,则向树中添加一个分支。对前面数据中前两条进行添加的过程如下:
构建树的详细代码如下:
def create_tree(dataSet,minSup=1):
#dataSet较为特殊,它是一个字典形式,存放项集和对应的出现次数,在一开始出现次数都为1
#minSup为最小支持度
headerTable={
} #存放头指针表
#第一次遍历数据集
for tran in dataSet: #由于dataSet是字典,这个循环是对于字典中的每一个键值
for item in tran: #对于键值中的每一个元素
headerTable[item]=headerTable.get(item,0)+dataSet[tran] #更新该元素在全局中出现的次数
#第一次遍历结束
for key in list(headerTable.keys()): #元素去重,对于每一个候元素来说
if headerTable[key]<minSup: #如果当前元素支持度小于最小值
del(headerTable[key]) #去除
freItemSet=set(headerTable.keys()) #去重后的元素
if len(freItemSet)==0: return None,None