前言node
對於如何發現一個數據集中的頻繁項集,前文講解的經典 Apriori 算法可以作到。算法
然而,對於每一個潛在的頻繁項,它都要檢索一遍數據集,這是比較低效的。在實際的大數據應用中,這麼作就更很差了。數據結構
本文將介紹一種專門檢索頻繁項集的新算法 - FP-growth 算法。架構
它只會掃描數據集兩次,能循序挖掘出頻繁項集。所以這種算法在網頁信息處理中佔據着很是重要的地位。app
FP-growth 算法基本原理機器學習
將數據存儲到一種成爲 FP 樹的數據結構中,這樣的一棵樹包含了數據集中知足最小支持度閾值的全部節點信息以及對應的支持度信息。函數
後面會專門講解如何實現這樣的一棵樹。oop
構建好了 FP 樹以後,經過必定規則遍歷這棵樹就能挖掘出頻繁項集。學習
後面也會專門講解如何遍歷 FP 樹。測試
FP 樹
下圖爲 FP 樹架構示意圖:
右邊部分中,"z:5" 的意思是,z在這條路徑上出現了5次(包含其單個出現,也包含組合出現的狀況)。
順着 "z:5" 往下,看到的是 r=1。這表示在這條路徑上 "zr" 出現了一次。
每一個節點在樹中都不是惟一的,好比可以清楚看到 r 出現了好幾回。
左邊的部分,存放的是全部原子項出現的個數(包含其單個出現,也包含組合出現的狀況),同時它還 」派生" 出一個鏈表將全部樹結構部分中的相同原子項串起來。
好比左邊部分 r=3 就派生出一個鏈表串起了 3 個 r 節點,每一個 r 節點標記都爲1,故左邊部分 r=3。
構建 FP 樹
FP 樹的機構稍微有點複雜,所以就不用字典,轉用類(對象)來存樹了。
下面是 FP 樹結構的代碼實現:
1 class treeNode: 2 'FP 樹節點' 3 4 #===================================== 5 # 輸入: 6 # nameValue: 節點名稱 7 # numOccur: 節點路徑出現次數 8 # parentNode: 父節點 9 #===================================== 10 def __init__(self, nameValue, numOccur, parentNode): 11 '初始化函數' 12 13 # 節點名字 14 self.name = nameValue 15 # 節點的路徑出現次數 16 self.count = numOccur 17 # 指向類似節點 18 self.nodeLink = None 19 # 父節點 20 self.parent = parentNode 21 # 子節點 22 self.children = {} 23 24 #===================================== 25 # 輸入: 26 # numOccur: 節點路徑出現次數增量 27 #===================================== 28 def inc(self, numOccur): 29 '增長節點出現次數' 30 31 # 增長這個節點的路徑出現次數 32 self.count += numOccur 33 34 #===================================== 35 # 輸入: 36 # ind: 節點路徑出現次數增量 37 # ind: 子樹序號 38 #===================================== 39 def disp(self, ind=1): 40 '將樹以文本形式顯示' 41 42 print ' '*ind, self.name, ' ', self.count 43 for child in self.children.values(): 44 child.disp(ind+1)
以上代碼定義的基本的 FP 樹節點數據結構。接下來就是樹生成部分以及表頭(見上一部分圖中左部)生成部分代碼:
1 def loadSimpDat(): 2 '載入測試數據' 3 4 simpDat = [['r', 'z', 'h', 'j', 'p'], 5 ['z', 'y', 'x', 'w', 'v', 'u', 't', 's'], 6 ['z'], 7 ['r', 'x', 'n', 'o', 's'], 8 ['y', 'r', 'x', 'z', 'q', 't', 'p'], 9 ['y', 'z', 'x', 'e', 'q', 's', 't', 'm']] 10 return simpDat 11 12 def createInitSet(dataSet): 13 '將測試數據格式化爲字典' 14 15 retDict = {} 16 for trans in dataSet: 17 retDict[frozenset(trans)] = 0 18 for trans in dataSet: 19 retDict[frozenset(trans)] += 1 20 return retDict 21 22 #===================================== 23 # 輸入: 24 # dataSet: 數據集 25 # minSup: 最小支持度(實際爲次數) 26 # 輸出: 27 # retTree: FP 樹結構 28 # headerTable: 表結構 29 #===================================== 30 def createTree(dataSet, minSup=1): 31 '建立 FP 樹及其對應表結構' 32 33 # 連續兩次遍歷數據集。第一次獲取全部數據項及個數;第二次會支持度過濾。 34 # 單元素頻繁集(含出現次數) 35 headerTable = {} 36 for trans in dataSet: 37 for item in trans: 38 headerTable[item] = headerTable.get(item, 0) + dataSet[trans] 39 for k in headerTable.keys(): 40 if headerTable[k] < minSup: 41 del(headerTable[k]) 42 43 # 單元素頻繁集(不含次數) 44 freqItemSet = set(headerTable.keys()) 45 # 沒有合乎要求的數據項則退出 46 if len(freqItemSet) == 0: 47 return None, None 48 49 # 對錶數據結構進行格式化,使之可以存放指針。 50 for k in headerTable: 51 headerTable[k] = [headerTable[k], None] 52 53 # 新建初始化樹節點 54 retTree = treeNode('Null Set', 1, None) 55 for tranSet, count in dataSet.items(): 56 57 # 當前事務的單元素集(含次數) 58 localD = {} 59 for item in tranSet: 60 if item in freqItemSet: 61 localD[item] = headerTable[item][0] 62 63 if len(localD) > 0: 64 # 對localD中全部元素進行排序 65 orderedItems = [v[0] for v in sorted(localD.items(), key=lambda p: p[1], reverse=True)] 66 # 更新 FP 樹 67 updateTree(orderedItems, retTree, headerTable, count) 68 69 # 返回FP樹和表頭結構 70 return retTree, headerTable 71 72 #===================================== 73 # 輸入: 74 # items: 事務項 75 # inTree: FP 樹 76 # headerTable: 表結構 77 # count: 事務項的個數 78 #===================================== 79 def updateTree(items, inTree, headerTable, count): 80 'FP 樹生長函數' 81 82 # 檢查事務項中的第一個元素是否做爲樹的直接子節點存在 83 if items[0] in inTree.children: 84 # 存在則直接更新子樹節點 85 inTree.children[items[0]].inc(count) 86 else: 87 # 不存在則更新樹結構 88 inTree.children[items[0]] = treeNode(items[0], count, inTree) 89 # 更新表結構 90 if headerTable[items[0]][1] == None: 91 headerTable[items[0]][1] = inTree.children[items[0]] 92 else: 93 updateHeader(headerTable[items[0]][1], inTree.children[items[0]]) 94 95 # 遞歸調用此樹生長函數 96 if len(items) > 1: 97 updateTree(items[1::], inTree.children[items[0]], headerTable, count) 98 99 #===================================== 100 # 輸入: 101 # nodeToTest: 指定表結構中的成員 102 # targetNode: 待加入鏈表節點的指針 103 #===================================== 104 def updateHeader(nodeToTest, targetNode): 105 '表結構更新函數' 106 107 while (nodeToTest.nodeLink != None): 108 nodeToTest = nodeToTest.nodeLink 109 110 nodeToTest.nodeLink = targetNode 111 112 def main(): 113 'FP 樹構建與展現' 114 115 # 載入測試數據 116 simpDat = loadSimpDat() 117 # 將測試數據格式化爲字典 118 initSet = createInitSet(simpDat) 119 # 建立 FP 樹及對應表結構 120 myFPTree, myHeaderTab = createTree(initSet, 3) 121 # 展現 FP 樹 122 myFPTree.disp()
測試結果:
從 FP 樹中挖掘頻繁項集
FP 樹構建好以後,就能對它進行挖掘,以找到全部頻繁項集。
挖掘過程僅僅針對 FP 樹和它對應的表結構,與原數據集沒有任何關係了。
挖掘的步驟以下:
1. 從 FP 樹中得到條件模式基
2. 利用條件模式基,得到一個條件 FP 樹。
3. 迭代 1,2 直到樹只剩下一個元素項。
下面分別講解如何抽取條件模式基,以及如何從條件模式基構建 FP 樹。
條件模式基的抽取
條件模式是指以所查找元素項爲結尾的路徑。
對於以下 FP 樹來講:
r 的條件模式基爲 {z},{x,s},{z,x,y} 。
而上圖的左部表結構就是用來求取抽象基的一個輔助結構。經過每一個子鏈表的每一個節點都能向上迭代獲取到一個路徑,一個鏈表獲得的全部路徑和就是該鏈表元素項對應的條件模式基。
以下部分代碼可用來求取元素項的條件模式基:
1 #===================================== 2 # 輸入: 3 # leafNode: 表結構的子鏈表的節點 4 # prefixPath: 待返回路徑 5 #===================================== 6 def ascendTree(leafNode, prefixPath): 7 '獲取FP樹某個葉子節點的前綴路徑' 8 9 if leafNode.parent != None: 10 prefixPath.append(leafNode.name) 11 ascendTree(leafNode.parent, prefixPath) 12 13 #===================================== 14 # 輸入: 15 # basePat: 元素項 16 # treeNode: 某個鏈表指向某首葉子節點的指針 17 #===================================== 18 def findPrefixPath(basePat, treeNode): 19 '獲取表結構中某個元素項的條件模式基' 20 21 # 條件模式基 22 condPats = {} 23 24 # 獲取某個元素項的條件模式基礎並返回 25 while treeNode != None: 26 prefixPath = [] 27 ascendTree(treeNode, prefixPath) 28 if len(prefixPath) > 1: 29 condPats[frozenset(prefixPath[1:])] = treeNode.count 30 treeNode = treeNode.nodeLink 31 32 return condPats
條件 FP 樹的構建與頻繁項集的挖掘
對於每一個頻繁項,都要建立一個條件 FP 樹。
條件 FP 樹的建立代碼,和以前的 FP 樹是同樣的。不過輸入數據集會變成第一次求出的條件模式。
並且,第一次挖掘的僅僅是最小元素項,然後面挖掘出的頻繁項都會基於上一步結果疊加。
如此一直迭代下去直到表結構爲空。
實現代碼以下:
1 #===================================== 2 # 輸入: 3 # inTree: 條件FP樹 4 # headerTable: 條件表頭結構 5 # minSup: 最小支持度 6 # preFix: 上一輪的頻繁項 7 # freqItemList: 頻繁項集 8 #===================================== 9 def mineTree(inTree, headerTable, minSup, preFix, freqItemList): 10 '挖掘頻繁項集' 11 12 # 對錶結構進行重排序(由小到大) 13 bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1])] 14 15 # 遍歷表結構 16 for basePat in bigL: 17 # 生成新頻繁子項 18 newFreqSet = preFix.copy() 19 newFreqSet.add(basePat) 20 # 加入頻繁集 21 freqItemList.append(newFreqSet) 22 # 挖掘新的條件模式基 23 condPattBases = findPrefixPath(basePat, headerTable[basePat][1]) 24 # 創建新的條件FP樹 25 myCondTree, myHead = createTree(condPattBases, minSup) 26 # 若表結構非空,遞歸挖掘。 27 if myHead != None: 28 mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)
測試結果:
小結
1. FP-growth 算法使用了新的數據結構,並且建立,遍歷過程遞歸代碼比較多,所以理解起來有點難度。
2. FP-growth 算法通常用來用來挖掘頻繁項集,不用來學習關聯規則。
3. 大數據領域中機器學習的部分就暫告一段落了(已學習完最爲經典的算法)。接下來的精力將主要放在 Hadoop 雲平臺的使用及其底層機制實現部分。