機器學習實戰---使用FP-growth演算法來高效發現頻繁項集
阿新 • • 發佈:2020-07-31
一:參考資料
(一)機器學習實戰
(二)以下3篇簡單瞭解
https://baijiahao.baidu.com/s?id=1661651339862291744&wfr=spider&for=pc(不全,但簡單)
https://blog.csdn.net/qq_40587575/article/details/79997195(稍微看看)
https://blog.csdn.net/qq1010885678/article/details/45244829(排版還可以)
(三)下面PPT不錯(可以完全理解,且案例豐富)
https://wenku.baidu.com/view/5a28e351bed126fff705cc1755270722182e594b.html
二:案例一(FP-growth演算法原理)
三:案例二(更詳細)尤其是頻繁集挖掘
四:FP樹結構定義
#一:FP樹結構定義 class treeNode: def __init__(self,nameValue,nameOccur,parentNode): #nameValue節點名稱,nameOccur計數,parentNode指向父節點 self.name = nameValue #節點名稱 self.count = nameOccur #計數器 self.nodeLink= None #用來存放節點資訊,用來指向下一個同類(同名稱)的節點 self.parent = parentNode #指向父節點 self.children = {} #子節點使用字典進行存放 def inc(self,numOccur): self.count += numOccur
五:資料載入
#二:載入資料 def loadSimDat(): 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 #返回字典型別{frozenset({'j', 'h', 'z', 'r', 'p'}): 1, frozenset({'y', 'v', 'x', 'w', 'u', 's', 't', 'z'}): 1...} return retDict
六:根據createInitSet方法得到的資料集型別,建立FP樹 和 FP條件樹
#三:根據createInitSet方法得到的資料集型別,建立FP樹 和 FP條件樹 #1.建樹主函式 def createTree(dataSet,minSup=1): headerTable = {} #存放頭指標表 for trans in dataSet: #遍歷資料,注意我們使用dataSet.items()可以遍歷(key,val)對,直接遍歷dataSet是對key的操作 for item in trans: #item是frozenset型別 headerTable[item] = headerTable.get(item,0) + dataSet[trans] #計數操作,其中dataSet[trans]返回字典對於values #重點:進行初次剪枝,去掉那些單個元素支援度太低的資料元素 keys = list(headerTable.keys()) for k in keys: if headerTable[k] < minSup: del(headerTable[k]) #達到剪枝效果 #檢查剪枝後是否還有資料存在 freqItemSet = set(headerTable.keys()) if len(freqItemSet) == 0: return None,None #因為我們在正常情況下,返回的資料是FP樹和頭指標表 #修改頭指標表的格式,使得可以指向同類節點(實現連結串列操作) for k in headerTable: headerTable[k] = [headerTable[k],None] #多出的None用來儲存後面需要的節點資訊 #先產生根節點 retTree = treeNode("Null Set",1,None) #節點名稱,出現次數,父節點 #開始建樹 for tranSet,count in dataSet.items(): localD = {} #用來儲存本樣本frozenset({'j', 'h', 'z', 'r', 'p'}): 1的keys是否是頻繁項集 for item in tranSet: if item in freqItemSet: #是在頻繁項集中 localD[item] = headerTable[item][0] #新增到localD儲存次數 if len(localD) > 0: #如果在本次樣本中有資料在頻繁項集中,則開始進行更新樹---重點 #對我們得到的臨時localD頻繁項集進行排序,將在更新樹按照出現最高順序進行更新(例如最大堆,但不是) orderItems = [v[0] for v in sorted(localD.items(),key=lambda p:p[1],reverse=True)] #大到小排序 updateTree(orderItems,retTree,headerTable,count) return retTree,headerTable #返回我們需要的樹和頭指標表 #2.按照傳入的一個樣本frozenset({'j', 'h', 'z', 'r', 'p'})《注意:是排序好的,大到小》,對樹進行更新 def updateTree(items,inTree,headerTable,count): """ 重點:我們使用遞迴方法建樹 :param items: 是傳入的排序好的樣本frozenset({'j', 'h', 'z', 'r', 'p'}) :param inTree: 是我們要建立的樹 :param headerTable: 是我們要對頭指標表進行修改節點指向, :param count: 是本樣本在資料集中出現的次數 """ if items[0] in inTree.children: #已經存在該增加計數 inTree.children[items[0]].inc(count) #增加計數 else: #開始進行新增節點 inTree.children[items[0]] = treeNode(items[0],count,inTree) #節點按照特定的資料結構修改 #由於我們有新增的節點,所以我們需要更新頭指標表中的nodeLink連結串列資訊,達到連結串列將同類型的節點連結 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) #3.更新頭指標表 def updateHeader(headerNode,newNode): while headerNode.nodeLink != None: headerNode = headerNode.nodeLink headerNode.nodeLink = newNode
七:開始從上面獲取的FP樹中,獲取各個資料的條件模式基
#四:開始從上面獲取的FP樹中,獲取各個資料的條件模式基 #1.從FP樹種獲取頭指標表中的各個資料的條件模式基(即找字首路徑,設定計數為當前節點計數值) def findPrefixPath(treeNode): #書本上多了一個引數??這裡直接傳我們指定的節點,查詢字首路徑 condPats = {} while treeNode != None: prefixPath = [] ascendTree(treeNode,prefixPath) #獲取字首路徑 #儲存字首路徑,和計數值 if len(prefixPath) > 1: #保證除了本身節點之外,還存在其他字首 condPats[frozenset(prefixPath[1:])] = treeNode.count #注意:使用frozenset型別作為key,並且儲存時不含有本身節點 treeNode = treeNode.nodeLink #遍歷下一個同類型節點 return condPats #返回所有的字首路徑 #2.向上遞迴遍歷樹,找字首節點資訊 def ascendTree(treeNode,prefixPath): #將結果儲存在prefixPath中 while treeNode.parent != None: #除了根節點(無用)之外,都進行新增節點 prefixPath.append(treeNode.name) treeNode = treeNode.parent
八:開始查詢頻繁集(注意:上面只對單個數據進行了一次支援度裁剪,下面會對各個組合進行裁剪)
#五:開始查詢頻繁集(注意:上面只對單個數據進行了一次支援度裁剪,下面會對各個組合進行裁剪) #根據條件基模型來構建條件FP樹 def mineTree(headerTable,minSup,preFix,freqItemList): """ :param headerTable: 是我們生成FP樹時,生成的頭指標表 :param minSup: 最小支援度,用來對複合的資料進行篩選 :param preFix: 首次為空集合,用來儲存當前字首 :param freqItemList: 首次為空列表,用來儲存所有的頻繁項集 """ #對頭指標表中的元素按照支援度(出現頻率)從小到大排序 bigL = [v[0] for v in sorted(headerTable.items(),key=lambda p:p[1][0])] #從滿足最小支援度的頭指標表的最小元素開始,遍歷頭指標表,挖掘頻繁資料項 for basePat in bigL: #最好只思考一次迴圈資料 #儲存當前字首路徑 newFreqSet = preFix.copy() #將當前頻繁元素加入頻繁集合中(這裡就是將頭指標表元素加入---已經是滿足的) newFreqSet.add(basePat) #都是frozenset型別---------注意: #1.newFreqSet.add(basePat)這一步我們可以看案例I5推導,這裡先加入I5,再獲取條件FP樹, #之後根據HeaderTable判斷是否為None #由案例可知,返回非None, #2.對headerTable中I2 I1建樹,newFreqSet中含有I5,再分別加上I1,I2 #2.1先加上I1,即得到I1 I5 #之後根據HeaderTable判斷是否為None #由案例可知,返回非None, #2.1.1對headerTable中I2建樹,newFreqSet中含有I1 I5,再加上I2 即可得到I2 I1 I5 #2.2再在I5基礎上,加上I2,即可得到I2 I5 #之後根據HeaderTable判斷是否為None #由案例可知,返回None,退出 #得到I2 I5, I1 I5, I2 I1 I5 #將每個頻繁元素新增到頻繁項集列表中 freqItemList.append(newFreqSet) #將每個頻繁項,都呼叫findPrefixPath函式,找到字首路徑 condPattBases = findPrefixPath(headerTable[basePat][1]) #重點:根據當前元素項生成的字首路徑和最小支援度,生成條件FP樹(內部會增加計數值) myCondTree,myHead = createTree(condPattBases,minSup) #從這裡可以看出,我們要儲存原始資料和字首路徑發現函式返回相同資料型別 #如果條件FP樹中有元素項,則可以再次遞迴生成條件樹 if myHead != None: #所以對於我們新的myHead必須在使用條件模型基時,滿足最小支援度 #遞迴挖掘該條件樹,每次向上推一個節點(條件模型基都不包含本節點,所以一直在向上走) mineTree(myHead,minSup,newFreqSet,freqItemList)
九:結果測試
dataSet = createInitSet(loadSimDat()) tree,headertable = createTree(dataSet,3) #注意:因為我們建樹的時候,支援度相同的節點不止一個,比如t:3,s:3,所以建樹結果順序可能不同 freqItemList = [] mineTree(headertable,3,set([]),freqItemList) print(freqItemList)
[{'r'}, {'s'}, {'x', 's'}, {'y'}, {'z', 'y'}, {'x', 'y'}, {'z', 'x', 'y'}, {'t'}, {'z', 't'}, {'x', 't'}, {'z', 'x', 't'}, {'y', 't'}, {'z', 'y', 't'}, {'y', 't', 'x'}, {'z', 'y', 't', 'x'}, {'x'}, {'z', 'x'}, {'z'}]
全部程式碼:
#一:FP樹結構定義 class treeNode: def __init__(self,nameValue,nameOccur,parentNode): #nameValue節點名稱,nameOccur計數,parentNode指向父節點 self.name = nameValue #節點名稱 self.count = nameOccur #計數器 self.nodeLink = None #用來存放節點資訊,用來指向下一個同類(同名稱)的節點 self.parent = parentNode #指向父節點 self.children = {} #子節點使用字典進行存放 def inc(self,numOccur): self.count += numOccur #二:載入資料 def loadSimDat(): 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 #返回字典型別{frozenset({'j', 'h', 'z', 'r', 'p'}): 1, frozenset({'y', 'v', 'x', 'w', 'u', 's', 't', 'z'}): 1...} return retDict #三:根據createInitSet方法得到的資料集型別,建立FP樹 和 FP條件樹 #1.建樹主函式 def createTree(dataSet,minSup=1): headerTable = {} #存放頭指標表 for trans in dataSet: #遍歷資料,注意我們使用dataSet.items()可以遍歷(key,val)對,直接遍歷dataSet是對key的操作 for item in trans: #item是frozenset型別 headerTable[item] = headerTable.get(item,0) + dataSet[trans] #計數操作,其中dataSet[trans]返回字典對於values #重點:進行初次剪枝,去掉那些單個元素支援度太低的資料元素 keys = list(headerTable.keys()) for k in keys: if headerTable[k] < minSup: del(headerTable[k]) #達到剪枝效果 #檢查剪枝後是否還有資料存在 freqItemSet = set(headerTable.keys()) if len(freqItemSet) == 0: return None,None #因為我們在正常情況下,返回的資料是FP樹和頭指標表 #修改頭指標表的格式,使得可以指向同類節點(實現連結串列操作) for k in headerTable: headerTable[k] = [headerTable[k],None] #多出的None用來儲存後面需要的節點資訊 #先產生根節點 retTree = treeNode("Null Set",1,None) #節點名稱,出現次數,父節點 #開始建樹 for tranSet,count in dataSet.items(): localD = {} #用來儲存本樣本frozenset({'j', 'h', 'z', 'r', 'p'}): 1的keys是否是頻繁項集 for item in tranSet: if item in freqItemSet: #是在頻繁項集中 localD[item] = headerTable[item][0] #新增到localD儲存次數 if len(localD) > 0: #如果在本次樣本中有資料在頻繁項集中,則開始進行更新樹---重點 #對我們得到的臨時localD頻繁項集進行排序,將在更新樹按照出現最高順序進行更新(例如最大堆,但不是) orderItems = [v[0] for v in sorted(localD.items(),key=lambda p:p[1],reverse=True)] #大到小排序 updateTree(orderItems,retTree,headerTable,count) return retTree,headerTable #返回我們需要的樹和頭指標表 #2.按照傳入的一個樣本frozenset({'j', 'h', 'z', 'r', 'p'})《注意:是排序好的,大到小》,對樹進行更新 def updateTree(items,inTree,headerTable,count): """ 重點:我們使用遞迴方法建樹 :param items: 是傳入的排序好的樣本frozenset({'j', 'h', 'z', 'r', 'p'}) :param inTree: 是我們要建立的樹 :param headerTable: 是我們要對頭指標表進行修改節點指向, :param count: 是本樣本在資料集中出現的次數 """ if items[0] in inTree.children: #已經存在該增加計數 inTree.children[items[0]].inc(count) #增加計數 else: #開始進行新增節點 inTree.children[items[0]] = treeNode(items[0],count,inTree) #節點按照特定的資料結構修改 #由於我們有新增的節點,所以我們需要更新頭指標表中的nodeLink連結串列資訊,達到連結串列將同類型的節點連結 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) #3.更新頭指標表 def updateHeader(headerNode,newNode): while headerNode.nodeLink != None: headerNode = headerNode.nodeLink headerNode.nodeLink = newNode #四:開始從上面獲取的FP樹中,獲取各個資料的條件模式基 #1.從FP樹種獲取頭指標表中的各個資料的條件模式基(即找字首路徑,設定計數為當前節點計數值) def findPrefixPath(treeNode): #書本上多了一個引數??這裡直接傳我們指定的節點,查詢字首路徑 condPats = {} while treeNode != None: prefixPath = [] ascendTree(treeNode,prefixPath) #獲取字首路徑 #儲存字首路徑,和計數值 if len(prefixPath) > 1: #保證除了本身節點之外,還存在其他字首 condPats[frozenset(prefixPath[1:])] = treeNode.count #注意:使用frozenset型別作為key,並且儲存時不含有本身節點 treeNode = treeNode.nodeLink #遍歷下一個同類型節點 return condPats #返回所有的字首路徑 #2.向上遞迴遍歷樹,找字首節點資訊 def ascendTree(treeNode,prefixPath): #將結果儲存在prefixPath中 while treeNode.parent != None: #除了根節點(無用)之外,都進行新增節點 prefixPath.append(treeNode.name) treeNode = treeNode.parent #五:開始查詢頻繁集(注意:上面只對單個數據進行了一次支援度裁剪,下面會對各個組合進行裁剪) #根據條件基模型來構建條件FP樹 def mineTree(headerTable,minSup,preFix,freqItemList): """ :param headerTable: 是我們生成FP樹時,生成的頭指標表 :param minSup: 最小支援度,用來對複合的資料進行篩選 :param preFix: 首次為空集合,用來儲存當前字首 :param freqItemList: 首次為空列表,用來儲存所有的頻繁項集 """ #對頭指標表中的元素按照支援度(出現頻率)從小到大排序 bigL = [v[0] for v in sorted(headerTable.items(),key=lambda p:p[1][0])] #從滿足最小支援度的頭指標表的最小元素開始,遍歷頭指標表,挖掘頻繁資料項 for basePat in bigL: #最好只思考一次迴圈資料 #儲存當前字首路徑 newFreqSet = preFix.copy() #將當前頻繁元素加入頻繁集合中(這裡就是將頭指標表元素加入---已經是滿足的) newFreqSet.add(basePat) #都是frozenset型別---------注意: #1.newFreqSet.add(basePat)這一步我們可以看案例I5推導,這裡先加入I5,再獲取條件FP樹, #之後根據HeaderTable判斷是否為None #由案例可知,返回非None, #2.對headerTable中I2 I1建樹,newFreqSet中含有I5,再分別加上I1,I2 #2.1先加上I1,即得到I1 I5 #之後根據HeaderTable判斷是否為None #由案例可知,返回非None, #2.1.1對headerTable中I2建樹,newFreqSet中含有I1 I5,再加上I2 即可得到I2 I1 I5 #2.2再在I5基礎上,加上I2,即可得到I2 I5 #之後根據HeaderTable判斷是否為None #由案例可知,返回None,退出 #得到I2 I5, I1 I5, I2 I1 I5 #將每個頻繁元素新增到頻繁項集列表中 freqItemList.append(newFreqSet) #將每個頻繁項,都呼叫findPrefixPath函式,找到字首路徑 condPattBases = findPrefixPath(headerTable[basePat][1]) #重點:根據當前元素項生成的字首路徑和最小支援度,生成條件FP樹(內部會增加計數值) myCondTree,myHead = createTree(condPattBases,minSup) #從這裡可以看出,我們要儲存原始資料和字首路徑發現函式返回相同資料型別 #如果條件FP樹中有元素項,則可以再次遞迴生成條件樹 if myHead != None: #所以對於我們新的myHead必須在使用條件模型基時,滿足最小支援度 #遞迴挖掘該條件樹,每次向上推一個節點(條件模型基都不包含本節點,所以一直在向上走) mineTree(myHead,minSup,newFreqSet,freqItemList) dataSet = createInitSet(loadSimDat()) tree,headertable = createTree(dataSet,3) #注意:因為我們建樹的時候,支援度相同的節點不止一個,比如t:3,s:3,所以建樹結果順序可能不同 freqItemList = [] mineTree(headertable,3,set([]),freqItemList) print(freqItemList)View Code
十:再回頭去看案例二,更好的理解八中挖掘頻繁集
https://wenku.baidu.com/view/5a28e351bed126fff705cc1755270722182e594b.html
本文較為簡略,後面需要再次理解。最近時間緊,任務重....,沒有結合關聯分析