注意:此方法筆者已經試過,計算關聯詞,在跑大批量數據模型的時候,很消耗內存。打個比方,如果你的數據集足夠大,內存全部能吃完。具體一點,每個子列表有10個詞,共500個子列表,16個G不夠用的,可以想象上萬條以上是什麼情況。
改進的方法:
1、upgraded FP-growsth, UFP 算法
http://www.bjutxuebao.com/bjgydx/article/2016/0254-0037-42-5-697.html#outline_anchor_19
2、頻繁模式挖掘中Apriori、FP-Growth和Eclat算法的實現和對比(Python實現)
https://www.cnblogs.com/infaraway/p/6774521.html
FP-growth算法理解
FP-growth(Frequent Pattern Tree, 頻繁模式樹),是韓家煒老師提出的挖掘頻繁項集的方法,是將數據集存儲在一個特定的稱作FP樹的結構之後發現頻繁項集或頻繁項對,即常在一塊出現的元素項的集合FP樹。
FP-growth算法比Apriori算法效率更高,在整個算法執行過程中,只需遍歷數據集2次,就能夠完成頻繁模式發現,其發現頻繁項集的基本過程如下:
(1)構建FP樹
(2)從FP樹中挖掘頻繁項集
FP-growth的一般流程如下:
1:先掃描一遍數據集,得到頻繁項爲1的項目集,定義最小支持度(項目出現最少次數),刪除那些小於最小支持度的項目,然後將原始數據集中的條目按項目集中降序進行排列。
2:第二次掃描,創建項頭表(從上往下降序),以及FP樹。
3:對於每個項目(可以按照從下往上的順序)找到其條件模式基(CPB,conditional patten base),遞歸調用樹結構,刪除小於最小支持度的項。如果最終呈現單一路徑的樹結構,則直接列舉所有組合;非單一路徑的則繼續調用樹結構,直到形成單一路徑即可。
示例說明
如下表所示數據清單(第一列爲購買id,第二列爲物品項目):
Tid | Items |
---|---|
1 | 1, I2, I5 |
2 | I2, I4 |
3 | I2, I3 |
4 | I1, I2, I4 |
5 | I1, I3 |
6 | I2, I3 |
7 | I1, I3 |
8 | I1, I2, I3, I5 |
9 | I1, I2, I3 |
第一步:構建FP樹
1、 掃描數據集,對每個物品進行計數:
1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|
6 | 7 | 6 | 2 | 2 |
2、 設定最小支持度(即物品最少出現的次數)爲2
3、按降序重新排列物品集(如果出現計數小於2的物品則需刪除)
2 | 1 | 3 | 4 | 5 |
---|---|---|---|---|
7 | 6 | 6 | 2 | 2 |
4、 根據項目(物品)出現的次數重新調整物品清單
Tid | Items |
---|---|
1 | I2, I1, I5 |
2 | I2, I4 |
3 | I2, I3 |
4 | I2, I1, I4 |
5 | I1, I3 |
6 | I2, I3 |
7 | I1, I3 |
8 | I2, I1, I3, I5 |
9 | I2, I1, I3 |
5、構建FP樹
加入第一條清單(I2, I1, I5):
加入第二條清單(I2, I4):
出現相同的節點進行累加(I2)
下面依次加入第3-9條清單,得到FP樹:
第二步:挖掘頻繁項集
對於每一個元素項,獲取其對應的條件模式基(conditional pattern base)。條件模式基是以所查找元素項爲結尾的路徑集合。每一條路徑其實都是一條前綴路徑。
按照從下往上的順序,考慮兩個例子。
(1)考慮I5,得到條件模式基{(I2 I1:1), (I2 I1 I3)}, 構造條件FP樹如下,然後遞歸調用FP-growth,模式後綴爲I5。這個條件FP樹是單路徑的,在FP-growth中直接列舉{I2:2,I1:2,I3:1}的所有組合,之後和模式後綴I5取並集得到支持度大於2的所有模式:{ I2 I5:2, I1 I5:2, I2 I1 I5:2}。
(2)I5的情況是比較簡單的,因爲I5對應的條件FP-樹是單路徑的。下面考慮I3,I3的條件模式基是{(I2 I1:2), (I2:2), (I1:2)},生成的條件FP-樹如左下圖,然後遞歸調用FP-growth,模式前綴爲I3。I3的條件FP-樹仍然是一個多路徑樹,首先把模式後綴I3和條件FP樹中的項頭表中的每一項取並集,得到一組模式{I2 I3:4, I1 I3:4},但是這一組模式不是後綴爲I3的所有模式。還需要遞歸調用FP-growth,模式後綴爲{I1,I3},{I1,I3}的條件模式基爲{I2:2},其生成的條件FP-樹如右下圖所示。這是一個單路徑的條件FP-樹,在FP-growth中把I2和模式後綴{I1,I3}取並得到模式{I1 I2 I3:2}。理論上還應該計算一下模式後綴爲{I2,I3}的模式集,但是{I2,I3}的條件模式基爲空,遞歸調用結束。最終模式後綴I3的支持度大於2的所有模式爲:{ I2 I3:4, I1 I3:4, I1 I2 I3:2}
根據FP-growth算法,最終得到的支持度大於2頻繁模式如下:
item | 條件模式基 | 條件FP樹 | 產生的頻繁模式 |
---|---|---|---|
I5 | {(I2 I1:1),(I2 I1 I3:1)} | (I2:2, I1:2) | I2 I5:2, I1 I5:2, I2 I1 I5:2 |
I4 | {(I2 I1:1), (I2:1)} | (I2:2) | I2 I4:2 |
I3 | {(I2 I1:2), (I2:2), (I1:2)} | (I2:4, I1:2), (I1:2) | I2 I3:4, I1 I3:4, I2 I1 I3:2 |
I1 | {(I2:4)} | (I2:4) | I2 I1:4 |
FP-growth算法實現:
1. FP樹的類定義
class treeNode:
'''
FP樹的類定義
'''
def __init__(self, nameValue, numOccur, parentNode):
self.name = nameValue #節點名字
self.count = numOccur #節點計數值
self.nodeLink = None #用於鏈接相似的元素項
self.parent = parentNode #needs to be updated
self.children = {} #子節點
def inc(self, numOccur):
'''
對count變量增加給定值
'''
self.count += numOccur
def disp(self, ind=1):
'''
將樹以文本形式展示
'''
# print (' '*ind, self.name, ' ', self.count)
for child in self.children.values():
child.disp(ind+1)
2.FP樹構建函數
def createTree(dataSet, minSup=1):
'''
創建FP樹
'''
headerTable = {}
#第一次掃描數據集
for trans in dataSet:#計算item出現頻數
for item in trans:
headerTable[item] = headerTable.get(item, 0) + dataSet[trans]
headerTable = {k:v for k,v in headerTable.items() if v >= minSup}
freqItemSet = set(headerTable.keys())
#print ('freqItemSet: ',freqItemSet)
if len(freqItemSet) == 0: return None, None #如果沒有元素項滿足要求,則退出
for k in headerTable:
headerTable[k] = [headerTable[k], None] #初始化headerTable
#print ('headerTable: ',headerTable)
#第二次掃描數據集
retTree = treeNode('Null Set', 1, None) #創建樹
for tranSet, count in dataSet.items():
localD = {}
for item in tranSet: #put transaction items in order
if item in freqItemSet:
localD[item] = headerTable[item][0]
if len(localD) > 0:
orderedItems = [v[0] for v in sorted(localD.items(), key=lambda p: p[1], reverse=True)]
updateTree(orderedItems, retTree, headerTable, count)#將排序後的item集合填充的樹中
return retTree, headerTable #返回樹型結構和頭指針表
def updateTree(items, inTree, headerTable, count):
if items[0] in inTree.children:#檢查第一個元素項是否作爲子節點存在
inTree.children[items[0]].inc(count) #存在,更新計數
else: #不存在,創建一個新的treeNode,將其作爲一個新的子節點加入其中
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):
'''
this version does not use recursion
Do not use recursion to traverse a linked list!
更新頭指針表,確保節點鏈接指向樹中該元素項的每一個實例
'''
while (nodeToTest.nodeLink != None):
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNode
3. 抽取條件模式基
def ascendTree(leafNode, prefixPath): #迭代上溯整棵樹
if leafNode.parent != None:
prefixPath.append(leafNode.name)
ascendTree(leafNode.parent, prefixPath)
def findPrefixPath(basePat, treeNode): #treeNode comes from header table
condPats = {}
while treeNode != None:
prefixPath = []
ascendTree(treeNode, prefixPath)
if len(prefixPath) > 1:
condPats[frozenset(prefixPath[1:])] = treeNode.count
treeNode = treeNode.nodeLink
return condPats
4. 遞歸查找頻繁項集
def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1][0])]# 1.排序頭指針表
for basePat in bigL: #從頭指針表的底端開始
newFreqSet = preFix.copy()
newFreqSet.add(basePat)
# print ('finalFrequent Item: ',newFreqSet) #添加的頻繁項列表
freqItemList.append(newFreqSet)
condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
# print ('condPattBases :',basePat, condPattBases)
# 2.從條件模式基創建條件FP樹
myCondTree, myHead = createTree(condPattBases, minSup)
# print ('head from conditional tree: ', myHead)
if myHead != None: # 3.挖掘條件FP樹
# print ('conditional tree for: ',newFreqSet)
myCondTree.disp(1)
mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)
5. 測試結果
def loadSimpDat():
simpDat = [
['中國','朝鮮','巴基斯坦'],
['朝鮮','韓國'],
['中國','日本'],
['美國','日本','韓國'],
['中國','巴基斯坦'],
['日本','美國'],
['中國','日本','韓國']
]
return simpDat
def createInitSet(dataSet):
retDict = {}
for trans in dataSet:
retDict[frozenset(trans)] = retDict.get(frozenset(trans), 0) + 1 #若沒有相同事項,則爲1;若有相同事項,則加1
return retDict
minSup = 2
simpDat = loadSimpDat()
initSet = createInitSet(simpDat)
myFPtree, myHeaderTab = createTree(initSet, minSup)
myFPtree.disp()
myFreqList = []
mineTree(myFPtree, myHeaderTab, minSup, set([]), myFreqList)
print(myFreqList)
打印結果如下
[[{‘美國’}, 2], [{‘日本’, ‘美國’}, 2], [{‘朝鮮’}, 2], [{‘巴基斯坦’}, 2], [{‘中國’, ‘巴基斯坦’}, 2], [{‘韓國’}, 3], [{‘韓國’, ‘日本’}, 2], [{‘日本’}, 4], [{‘中國’, ‘日本’}, 2], [{‘中國’}, 4]]