第十二章 使用FP-Growth算法來高效發現頻繁項集
FP-growth算法,基於Apriori構建,但在完成相同任務時採用了不同的技術,其只需要對數據庫進行兩次掃描,而Apriori算法對於每個潛在的頻繁項集都會掃描數據集判定給定模式是否頻繁,因此其比Apriori算法快。FP算法需要將數據集存儲在一個特定的稱作FP樹的結構之後發現頻繁項集或者頻繁項對。
12.1 FP樹:用於編碼數據集的有效方式
FP-growth算法將數據存儲在一種稱爲FP樹的緊湊數據結構中。FP(Frequent Pattern)通過鏈接來連接相似元素,被連起來的元素項可以看成一個鏈表。
同搜索樹不同的是,一個元素項可以在一棵FP樹中出現多次。FP樹會存儲項集的出現頻率,而每個項集會以路徑的方式存儲在樹中。存在相似元素的集合會共享樹的一部分。只有當集合之間完全不同時,樹纔會分叉。樹節點上給出集合中的單個元素及其在序列中的出現次數,路徑會給出該序列的出現次數。相似項之間的鏈接即節點鏈接,用於快速發現相似項的位置。
FP-growth算法首先構建FP樹,然後利用它來挖掘頻繁項集。爲構建FP樹,需要對原始數據集掃描兩遍。第一遍對所有元素項的出現次數進行計數。第二遍掃描只考慮那些頻繁元素。
12.2 構建FP樹
數據結構:
FP樹比其他樹更加複雜,因此需要一個類來保存樹的每一個節點。
#!/usr/bin/env python # coding=utf-8 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) rootNode = treeNode("pyramid", 9, None) rootNode.children["eye"] = treeNode("eye",13, None) rootNode.disp() rootNode.children["phoenix"] = treeNode("phoenix", 3, None) rootNode.disp()
構建FP樹
除了FP樹的類,還需要頭指針表來指向給定類型的第一個實例。通過頭指針表可以快速訪問FP樹中一個給定類型的所有元素。可用字典保存,並且其value存放FP樹中每類元素的總數。
第一遍遍歷數據集得到每個元素項的出現頻率。去掉不滿足最小支持度的元素項。再構建FP樹。構建時,讀入每個項集並將其添加到一條已經存在的路徑中。如果該路徑不存在,則創建一條新路徑。每個事務就是一個無序集合。假設集合{z,x,y}和{y,z,r},那麼在FP樹中,相同項只會表示一次。爲此在將集合添加到樹之前,先對每個集合進行排序,排序基於元素項出現的頻率。
在對事務記錄過濾和排序之後,就可以構建FP樹了。從空集,向其中不斷添加頻繁項集。過濾、排序後的事務依次添加到樹中,如果樹中巳存在現有元素,則增加現有元素的值;如果現有元素不存在,則向樹添加一個分枝。
coding:
#================FP樹構建函數============================= def createTree(dataSet, minSup=1): headerTable = {} for trans in dataSet: #遍歷一遍掃描數據集並統計每個元素項出現的頻度 for item in trans: headerTable[item] = headerTable.get(item,0)+dataSet[trans] for k in 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 e:e[1], reverse =True)] updateTree(orderedItems, retTree, headerTable, count) #排序後,對樹進行填充 return retTree, headerTable def updateTree(items, inTree, headerTable, count): 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(items[1::], inTree.children[items[0]], headerTable, count) #添加了首節點,遞歸添加剩下的節點 def updateHeader(nodeToTest, targetNode): 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 simpDat = loadSimpDat() print simpDat initSet = createInitSet(simpDat) print initSet myFPtree, myHeaderTab = createTree(initSet, 3) myFPtree.disp()
效果
12.3 從一顆FP樹中挖掘頻繁項集
有了FP樹之後,就可以抽取頻繁項集了,首先從單元素項集開始,然後在此基礎上逐步構建更大的集合。
步驟:
抽取條件模式基
- 從FP樹中獲得條件模式基;
- 利用條件模式基,構建一個條件FP樹;
- 迭代重複步驟1,2,直到樹包含一個元素項爲止。
條件模式基是以所查找元素項爲結尾的路徑集合。每一條路徑其實都是一條前綴路徑。圖12-2中,符號r的前綴路徑有{x,s},{z,x,y}和{z}。根據頭指針表通過上溯樹直到根節點抽取出條件模式基。
創建條件FP樹
對於每一個頻繁項,都要創建一顆條件FP樹。通過遞歸可發現頻繁項、條件模式基以及另外的條件樹。假設以頻繁項t創建一個條件FP樹,然後對{t,y}、{t,x}、...等重複該過程。
coding:
#=========發現以給定元素項結尾的所有路徑的函數================= 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 #===============遞歸查找頻繁項集的mineTree函數================= def mineTree(inTree, headerTable, minSup, preFix, freqItemList): bigL = [v[0] for v in sorted(headerTable.items(), key=lambda e:e[1])] #從頭指針表的底端開始 for basePat in bigL: #bigL爲頭指針,basePat爲“t”,"r"等等 newFreqSet = preFix.copy() newFreqSet.add(basePat) freqItemList.append(newFreqSet) condPattBases = findPrefixPath(basePat, headerTable[basePat][1]) #創建“t”的條件模式基 myCondTree, myHead = createTree(condPattBases, minSup) #以條件模式基構建條件FP樹,得到的結果用於下一次迭代 if myHead != None: #myHead由createTree函數得到,本質是頭指針表變量。 print "conditional tree for: ", newFreqSet myCondTree.disp(1) mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList) freqItems = [] mineTree(myFPtree, myHeaderTab, 3, set([]), freqItems) print freqItems
效果:
12.4 示例:從新聞網站點擊流中挖掘
數據:
該文件中的每一行包含某個用戶瀏覽過的新聞報道。一些用戶只看過一篇報道,而有些用戶看過2498篇報道。用戶和報道被編碼成整數。
coding:
#========================從新聞網站點擊流中挖掘================ parsedDat = [line.split() for line in open("kosarak.dat").readlines()] initSet = createInitSet(parsedDat) myFPtree, myHeaderTab = createTree(initSet, 100000) myFreqList = [] mineTree(myFPtree, myHeaderTab, 100000, set([]), myFreqList) print len(myFreqList) print myFreqList
效果:
12.5 小結
FP-growth算法是一種用於發現數據集中頻繁模式的有效方法,利用Apriori原理,只對數據集掃描兩次,運行更快。在算法中,數據集存儲在FP樹中,構建完樹後,通過查找元素項的條件基及構建條件FP樹來發現頻繁項集。重複進行直到FP樹只包含一個元素爲止。
可以使用FP算法在多種文本中查找頻繁單詞。