机器学习实战 9-FP-growth算法

1 FP树

1.1  FP介绍

将数据结构存储在一种称为FP树的紧凑数据结构中,如图:

 

 

一般流程:

 

  1. 输入数据,构建出一个如上图所示的数据结构(可以理解为按每条数据集合一步一步建立出来的树),即FP树

  2. 从FP树中挖掘频繁项集

    1. 从FP树中获得条件模式基(以某查找元素为结尾的路径集合,如y元素的条件模式基为{[t,s,x,z],[t,r,x,z]})

    2. 利用条件模式基再构建条件FP树(这里是有频次阈值要求的)

    3. 迭代 a,b ,直到树中只包含一个元素为止。此时已经找到满足阈值条件所有频繁项集。

(阈值要求:即频繁次数的最低要求,如要求某元素或者集合至少出现100次)

1.2  与Apriori对比

之前学的关联算法是Apriori,它对每个潜在的频繁项集都会扫描数据集判定给定模式是否频繁,而此处介绍的FP-growth只需对数据库进行两次扫描,因此较快。当输入数据量比较大时,FP树优势较明显

2 python实现及图解

2.1 创建FP树的数据结构

包含存放节点的名字及计数值,nodeLink用于存放链接元素。

 1 # 建立FP树的类定义
 2 class treeNode:
 3     def __init__(self,nameValue,numOccur,parrentNode):
 4         self.name=nameValue            # 存放节点名字
 5         self.count=numOccur            # 存放计数值
 6         self.nodeLink=None             # 链接相似的元素项
 7         self.parent=parrentNode        # 指向父节点
 8         self.children={}               # 存放子节点
 9     def inc(self,numOccur):
10         self.count +=numOccur
11     def disp(self,ind=1):
12         print(' '*ind,self.name,' ',self.count)
13         for child in self.children.values():
14             child.disp(ind+1)

2.2 构建FP树

算法过程:

    1. 统计所有元素出现的频次,并删去不满足阈值的元素

    2. 创建一个头指针表headerTable,用来存放各元素总数,并存放指针(即位置)

    3. 遍历每一个集合

      1. 选取集合中满足要求的元素,并将它们顺序按照总频数从多到少的排列(orderedItem),从上往下建立树,并往headerTable中填充指针

      2. 当headerTable中某一元素已经含有指针时,如遍历到第四次时的x,此时利用updateHeader函数,用nodeLink链接(图4中x到x的蓝色虚线)

程序如下:

 1 # 建立FP树的类定义
 2 class treeNode:
 3     def __init__(self,nameValue,numOccur,parrentNode):
 4         self.name=nameValue            # 存放节点名字
 5         self.count=numOccur            # 存放计数值
 6         self.nodeLink=None             # 链接相似的元素项
 7         self.parent=parrentNode        # 指向父节点
 8         self.children={}               # 存放子节点
 9 
10     def inc(self,numOccur):
11         self.count +=numOccur
12 
13     def disp(self,ind=1):
14         print(' '*ind,self.name,' ',self.count)
15         for child in self.children.values():
16             child.disp(ind+1)
17 
18 # FP树构建函数
19 def createTree(dataSet, minSup=1):
20     # 使用字典作为数据结构,保存头指针
21     headerTable={}
22     # 遍历数据集,获得每个元素的出现频率
23     for trans in dataSet:
24         # print(trans)
25         for item in trans:
26             headerTable[item]=headerTable.get(item,0)+dataSet[trans]
27     # 删去出现频率少的小伙伴
28     for k in list(headerTable.keys()):
29         if headerTable[k]<minSup:
30             del(headerTable[k])
31     freqItemSet=set(headerTable.keys())
32     if len(freqItemSet)==0:
33         return None,None
34     for k in headerTable:
35         headerTable[k]=[headerTable[k],None]
36     reTree=treeNode('Null Set',1,None)
37     for tranSet,count in dataSet.items():
38         localD={}
39         for item in tranSet:
40             if item in freqItemSet:
41                 localD[item]=headerTable[item][0]
42         if len(localD)>0:
43             # 根据出现的频次对localD进行排序
44             orderdItems=[v[0] for v in sorted(localD.items(),key=lambda p:p[1],reverse=True)]
45             print(orderdItems)
46             updateTree(orderdItems,reTree,headerTable,count)
47     return reTree,headerTable
48 
49 def updateTree(items,inTree,headerTable,count):
50     # 判断是否子节点,如果是,更新计数,如果不是创建子节点
51     if items[0] in inTree.children:
52         inTree.children[items[0]].inc(count)
53     else:
54         inTree.children[items[0]]=treeNode(items[0],count,inTree)
55         if headerTable[items[0]][1]==None:
56             headerTable[items[0]][1]=inTree.children[items[0]]
57         else:
58             updateHeader(headerTable[items[0]][1],inTree.children[items[0]])
59     # 循环创建
60     if len(items)>1:
61         updateTree(items[1::],inTree.children[items[0]],headerTable,count)
62  # 更新指针链接
63 def updateHeader(nodeToTest,targetNode):
64     while (nodeToTest.nodeLink != None):
65         nodeToTest=nodeToTest.nodeLink
66     nodeToTest.nodeLink=targetNode
67 
68 def loadSimpDat():
69     simpDat = [['r', 'z', 'h', 'j', 'p'],
70                ['z', 'y', 'x', 'w', 'v', 'u', 't', 's'],
71                ['z'],
72                ['r', 'x', 'n', 'o', 's'],
73                ['y', 'r', 'x', 'z', 'q', 't', 'p'],
74                ['y', 'z', 'x', 'e', 'q', 's', 't', 'm']]
75     return simpDat
76 
77 # 将输入数据列表转换为字典
78 def createInitSet(dataSet):
79     retDict = {}
80     for trans in dataSet:
81         retDict[frozenset(trans)] = 1
82     return retDict
83 
84 simpDat=loadSimpDat()
85 # print(simpDat)
86 initSet=createInitSet(simpDat)
87 # print(initSet)
88 myFPtree,myHeaderTab=createTree(initSet,3)
89 a=myFPtree.disp()
90 print(myHeaderTab)
View Code

图解:

注意:

    1. 每一次都是从根本节点“Null Set”开始,往下建立

    2. 注意nodeLink保存位置,主要由updateHeader函数起作用

    3. 由于频次相同的元素较多(如r,t,y,s),每次生成树可能不太一样

2.3 抽取条件模式基

前面说过,条件模式基就是以所查找元素为结尾的路径集合。即以某元素为起点,往上追溯的路径集合。如上面 t 的条件模式基为 {{s,x,z}:2,{r,x,z}:1} 。

程序:

 1 # 递归回溯树,从本节点一直往上
 2 def ascendTree(leafNode,prefixPath):
 3     if leafNode.parent != None:
 4         prefixPath.append(leafNode.name)
 5         ascendTree(leafNode.parent,prefixPath)
 6 
 7 # 找到需查找元素的所有前缀路径
 8 def findPrefixPath(basePat,treeNode):
 9     condPats={}
10     while treeNode != None:
11         prefixPath=[]
12         ascendTree(treeNode,prefixPath)
13         if len(prefixPath)>1:
14             condPats[frozenset(prefixPath[1:])]=treeNode.count
15         treeNode=treeNode.nodeLink
16     return condPats
17 
18 simpDat=loadSimpDat()
19 # print(simpDat)
20 initSet=createInitSet(simpDat)
21 # print(initSet)
22 myFPtree,myHeaderTab=createTree(initSet,3)
23 a=myFPtree.disp()
24 print(myHeaderTab)
25 print(myFPtree)
26 for item in list(myHeaderTab.keys()):
27     print(findPrefixPath(item,myHeaderTab[item][1]))
View Code

2.4 创建条件FP树

利用条件模式基,创建FP树,........循环,直到树中只包含一个元素,这就是一个找频繁项集的过程(一元的、二元的、.....等等多元的,各种类型的,所有满足条件的组合)

 1 # 创建条件FP树
 2 def mineTree(inTree,headerTable,minSup,preFix,freqItemList):
 3     # 此处原文有错误
 4     # 从头指针的底端开始
 5     bigL=[v[0] for v in sorted(headerTable.items(),key=lambda p:p[0])]
 6     for basePat in bigL:
 7         newFreqSet=preFix.copy()
 8         newFreqSet.add(basePat)
 9         freqItemList.append(newFreqSet)
10         # 创建条件基
11         condPattBases=findPrefixPath(basePat,headerTable[basePat][1])
12         # 创造条件fp树
13         myCondTree,myHead=createTree(condPattBases,minSup)
14         # 递归找到满足阈值的项
15         # myHead==None 时,说明树只有一层叶节点,即频繁次项为一元 
16         if myHead!=None:
17             print('conditional tree for:',newFreqSet)
18             myCondTree.disp(1)
19             mineTree(myCondTree,myHead,minSup,newFreqSet,freqItemList)
20             
21 freqItems=[]
22 c=mineTree(myFPtree,myHeaderTab,3,set([]),freqItems)
23 print(freqItems)          
View Code

 

3 实例:从新闻网站点击流中挖掘

  1 # 1. 建立FP树的类定义
  2 class treeNode:
  3     def __init__(self,nameValue,numOccur,parrentNode):
  4         self.name=nameValue            # 存放节点名字
  5         self.count=numOccur            # 存放计数值
  6         self.nodeLink=None             # 链接相似的元素项
  7         self.parent=parrentNode        # 指向父节点
  8         self.children={}               # 存放子节点
  9 
 10     def inc(self,numOccur):
 11         self.count +=numOccur
 12 
 13     def disp(self,ind=1):
 14         print(' '*ind,self.name,' ',self.count)
 15         for child in self.children.values():
 16             child.disp(ind+1)
 17             
 18 # 2. 将输入数据列表转换为字典
 19 def createInitSet(dataSet):
 20     retDict = {}
 21     for trans in dataSet:
 22         retDict[frozenset(trans)] = 1
 23     return retDict            
 24 
 25 # 3. 构建FP树
 26 def createTree(dataSet, minSup=1):
 27     # 使用字典作为数据结构,保存头指针
 28     headerTable={}
 29     # 遍历数据集,获得每个元素的出现频率
 30     for trans in dataSet:
 31         # print(trans)
 32         for item in trans:
 33             headerTable[item]=headerTable.get(item,0)+dataSet[trans]
 34     # 删去出现频率少的小伙伴
 35     for k in list(headerTable.keys()):
 36         if headerTable[k]<minSup:
 37             del(headerTable[k])
 38     freqItemSet=set(headerTable.keys())
 39     if len(freqItemSet)==0:
 40         return None,None
 41     for k in headerTable:
 42         headerTable[k]=[headerTable[k],None]
 43     reTree=treeNode('Null Set',1,None)
 44     for tranSet,count in dataSet.items():
 45         localD={}
 46         for item in tranSet:
 47             if item in freqItemSet:
 48                 localD[item]=headerTable[item][0]
 49         if len(localD)>0:
 50             # 根据出现的频次对localD进行排序
 51             orderdItems=[v[0] for v in sorted(localD.items(),key=lambda p:p[1],reverse=True)]
 52             # print(orderdItems)
 53             updateTree(orderdItems,reTree,headerTable,count)
 54     return reTree,headerTable
 55 def updateTree(items,inTree,headerTable,count):
 56     # 判断是否子节点,如果是,更新计数,如果不是创建子节点
 57     if items[0] in inTree.children:
 58         inTree.children[items[0]].inc(count)
 59     else:
 60         inTree.children[items[0]]=treeNode(items[0],count,inTree)
 61         if headerTable[items[0]][1]==None:
 62             headerTable[items[0]][1]=inTree.children[items[0]]
 63         else:
 64             updateHeader(headerTable[items[0]][1],inTree.children[items[0]])
 65     # 循环创建
 66     if len(items)>1:
 67         updateTree(items[1::],inTree.children[items[0]],headerTable,count)
 68  # 更新指针链接
 69 def updateHeader(nodeToTest,targetNode):
 70     while (nodeToTest.nodeLink != None):
 71         nodeToTest=nodeToTest.nodeLink
 72     nodeToTest.nodeLink=targetNode
 73     
 74 # 4. 构建条件模式基
 75 # 递归回溯树,从本节点一直往上
 76 def ascendTree(leafNode,prefixPath):
 77     if leafNode.parent != None:
 78         prefixPath.append(leafNode.name)
 79         ascendTree(leafNode.parent,prefixPath)
 80 # 找到需查找元素的所有前缀路径
 81 def findPrefixPath(basePat,treeNode):
 82     condPats={}
 83     while treeNode != None:
 84         prefixPath=[]
 85         ascendTree(treeNode,prefixPath)
 86         if len(prefixPath)>1:
 87             condPats[frozenset(prefixPath[1:])]=treeNode.count
 88         treeNode=treeNode.nodeLink
 89     return condPats    
 90 
 91 #  5.创建条件FP树,找到频繁项
 92 def mineTree(inTree,headerTable,minSup,preFix,freqItemList):
 93     # 此处原文有错误
 94     # 从头指针的底端开始
 95     bigL=[v[0] for v in sorted(headerTable.items(),key=lambda p:p[0])]
 96     for basePat in bigL:
 97         newFreqSet=preFix.copy()
 98         newFreqSet.add(basePat)
 99         freqItemList.append(newFreqSet)
100         # 创建条件基
101         condPattBases=findPrefixPath(basePat,headerTable[basePat][1])
102         # 创造条件fp树
103         myCondTree,myHead=createTree(condPattBases,minSup)
104         # 递归找到满足阈值的项
105         # myHead==None 时,说明树只有一层叶节点,即频繁次项为一元
106         if myHead!=None:
107             print('conditional tree for:',newFreqSet)
108             myCondTree.disp(1)
109             mineTree(myCondTree,myHead,minSup,newFreqSet,freqItemList)
110 
111 parseDat=[line.split() for line in open('kosarak.dat').readlines()]
112 initSet=createInitSet(parseDat)
113 myFPtree,myHeaderTab=createTree(initSet,100000)
114 myFreqlist=[]
115 mineTree(myFPtree,myHeaderTab,100000,set([]),myFreqlist)
116 print(myFreqlist)            
View Code
大概运行了8秒钟,得到:

 

 

 

 

 

 

 

 

 
 
 
 
 
 
posted @ 2019-06-03 10:57  滇红88号  阅读(1373)  评论(0编辑  收藏  举报