上一章介紹了發(fā)現(xiàn)頻繁項(xiàng)集與關(guān)鍵規(guī)則的算法,本章將繼續(xù)關(guān)注發(fā)現(xiàn)頻繁項(xiàng)集這一任務(wù)。我們會(huì)深入探索該任務(wù)的解決方法,并應(yīng)用FP-growth算法進(jìn)行處理。這種算法雖然能更為高效地發(fā)現(xiàn)頻繁項(xiàng)集,但不能用于發(fā)現(xiàn)關(guān)聯(lián)規(guī)則。
FP-growth算法
優(yōu)點(diǎn):一般要快于Apriori
缺點(diǎn):實(shí)現(xiàn)比較困難,在某些數(shù)據(jù)集上性能會(huì)下降
適用數(shù)據(jù)類型:標(biāo)稱型數(shù)據(jù)
FP-growth算法講數(shù)據(jù)存儲(chǔ)在一種稱為FP樹(shù)的緊湊數(shù)據(jù)結(jié)構(gòu)中。FP代表頻繁模式,F(xiàn)requent pattern。同搜索樹(shù)不同的是,一個(gè)元素項(xiàng)可以在一棵FP樹(shù)中出現(xiàn)多次。FP樹(shù)會(huì)存儲(chǔ)項(xiàng)集的出現(xiàn)頻率,而每個(gè)項(xiàng)集會(huì)以路徑的方式存儲(chǔ)在樹(shù)中。
相似點(diǎn)之間的鏈接即節(jié)點(diǎn)鏈接(node link),用于快速發(fā)現(xiàn)相似的位置。
本章的FP樹(shù)比書(shū)中的其他樹(shù)更加復(fù)雜,因此要?jiǎng)?chuàng)建一個(gè)類來(lái)保存樹(shù)的每一個(gè)節(jié)點(diǎn):
#FP樹(shù)
class treeNode:
def __init__(self, nameValue, numOccur, parentNode):
self.name = nameValue
self.count = numOccur#計(jì)數(shù)器
self.nodeLink = None#用于鏈接相似的元素項(xiàng)
self.parent = parentNode#指向父節(jié)點(diǎn),需要被更新
self.children = {} #存放子節(jié)點(diǎn)
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)
先創(chuàng)建樹(shù)中的一個(gè)單節(jié)點(diǎn):
In [45]: import fpGrowth
...: rootNode = fpGrowth.treeNode('pyramid',9,None)
接下來(lái)增加一個(gè)子節(jié)點(diǎn):
In [46]: rootNode.children['eye'] = fpGrowth.treeNode('eye',13,None)
顯示子節(jié)點(diǎn):
In [47]: rootNode.disp()
pyramid 9
eye 13
再增加一個(gè)節(jié)點(diǎn)看看子節(jié)點(diǎn)的展示效果:
In [48]: rootNode.children['phoenix']=fpGrowth.treeNode('phoenix',3,None)
...: rootNode.disp()
...:
pyramid 9
eye 13
phoenix 3
除了剛剛給出的FP樹(shù)以外,還需要一個(gè)頭指針表來(lái)指向給定類型的第一個(gè)實(shí)例。這里使用一個(gè)字典作為數(shù)據(jù)結(jié)構(gòu),來(lái)保存頭指針表。除了存放指針外,頭指針表還可以用來(lái)存放FP樹(shù)中每類元素的總數(shù)。
接下來(lái),我們通過(guò)代碼來(lái)實(shí)現(xiàn)上述過(guò)程:
def createTree(dataSet, minSup=1): #數(shù)據(jù)集,最小支持度
headerTable = {}
#遍歷數(shù)據(jù)集兩次
for trans in dataSet:#統(tǒng)計(jì)
for item in trans:
headerTable[item] = headerTable.get(item, 0) + dataSet[trans]
for k in headerTable.keys(): #移除不滿足最小支持度的元素項(xiàng)
if headerTable[k] < minSup:
del(headerTable[k])
freqItemSet = set(headerTable.keys())
#print 'freqItemSet: ',freqItemSet
if len(freqItemSet) == 0: return None, None #都不滿足,則退出
for k in headerTable:
headerTable[k] = [headerTable[k], None] #修改為下一步準(zhǔn)備
#print 'headerTable: ',headerTable
retTree = treeNode('Null Set', 1, None) #根節(jié)點(diǎn)
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 p: p[1], reverse=True)]
updateTree(orderedItems, retTree, headerTable, count)#用排序后的頻率項(xiàng)進(jìn)行填充
return retTree, headerTable
def updateTree(items, inTree, headerTable, count):
if items[0] in inTree.children:#檢查第一個(gè)元素是否作為子節(jié)點(diǎn)存在
inTree.children[items[0]].inc(count) #更新計(jì)數(shù)
else: #新建一個(gè)子節(jié)點(diǎn)添加到樹(shù)中
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:#對(duì)剩下的元素項(xiàng)迭代調(diào)用,每一次奧調(diào)用去掉第一個(gè)元素
updateTree(items[1::], inTree.children[items[0]], headerTable, count)
def updateHeader(nodeToTest, targetNode): #確保節(jié)點(diǎn)鏈接指向樹(shù)中該元素項(xiàng)的每一個(gè)實(shí)例
while (nodeToTest.nodeLink != None): #直達(dá)鏈尾
nodeToTest = nodeToTest.nodeLink
nodeToTest.nodeLink = targetNode
下面將數(shù)據(jù)集和數(shù)據(jù)包裝器加入代碼中:
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
首先,導(dǎo)入數(shù)據(jù)庫(kù)實(shí)例:
In [2]: import fpGrowth
...: simpDat = fpGrowth.loadSimpDat()
...:
In [3]: simpDat
Out[3]:
[['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']]
接下來(lái)為了函數(shù)createTree(),需要對(duì)上面的數(shù)據(jù)進(jìn)行格式化處理:
In [4]: initSet = fpGrowth.createInitSet(simpDat)
...: initSet
...:
Out[4]:
{frozenset({'e', 'm', 'q', 's', 't', 'x', 'y', 'z'}): 1,
frozenset({'n', 'o', 'r', 's', 'x'}): 1,
frozenset({'z'}): 1,
frozenset({'s', 't', 'u', 'v', 'w', 'x', 'y', 'z'}): 1,
frozenset({'p', 'q', 'r', 't', 'x', 'y', 'z'}): 1,
frozenset({'h', 'j', 'p', 'r', 'z'}): 1}
于是可以通過(guò)如下 命令創(chuàng)建FP樹(shù):
In [10]: myFPtree, myHeaderTab = fpGrowth.createTree(initSet, 3)
...: myFPtree.disp()
...:
Null Set 1
x 1
s 1
r 1
z 5
x 3
y 3
s 2
t 2
r 1
t 1
r 1
上面給出的是元素項(xiàng)及其對(duì)應(yīng)的頻率計(jì)數(shù)值,其中每個(gè)縮進(jìn)表示所處的樹(shù)的深度。
現(xiàn)在我們已經(jīng)構(gòu)建了FP樹(shù),接下來(lái)就使用它進(jìn)行數(shù)據(jù)挖掘。
從FP樹(shù)中抽取頻繁項(xiàng)集的三個(gè)基本步驟如下:
(1)從FP樹(shù)中獲取條件模式基;
(2)利用條件模式基,構(gòu)建一個(gè)條件FP樹(shù);
(3)迭代重復(fù)步驟(1)和步驟(2),直到樹(shù)包含一個(gè)元素項(xiàng)為止。
首先從上一節(jié)發(fā)現(xiàn)的已經(jīng)保存在頭指針表中的單個(gè)頻繁元素項(xiàng)開(kāi)始。對(duì)于每一個(gè)元素項(xiàng),獲得其對(duì)于的條件模式基(conditional pattern base)。條件模式基是以所查找元素項(xiàng)為結(jié)尾的路徑集合。每一條路徑其實(shí)都是一條前綴路徑(prefixpath)。簡(jiǎn)而言之,一條前綴路徑是介于所查找元素項(xiàng)與樹(shù)根節(jié)點(diǎn)之間的所有內(nèi)容。
下面的程序清單給出了前綴路徑發(fā)現(xiàn)的代碼:
def ascendTree(leafNode, prefixPath): #迭代回溯整棵樹(shù)
if leafNode.parent != None:
prefixPath.append(leafNode.name)
ascendTree(leafNode.parent, prefixPath)
#遍歷鏈表直到到達(dá)結(jié)尾,每遇到一個(gè)元素項(xiàng)都會(huì)調(diào)用ascendTree()函數(shù)來(lái)上溯FP樹(shù),ing收集所有遇到的元素項(xiàng)的名稱
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
使用之前構(gòu)建的樹(shù)來(lái)看一下實(shí)際的運(yùn)行效果:
In [4]: import fpGrowth
...: fpGrowth.findPrefixPath('x',myHeaderTab['x'][1])
...:
Out[4]: {frozenset({'z'}): 3}
In [5]: fpGrowth.findPrefixPath('z',myHeaderTab['z'][1])
Out[5]: {}
In [6]: fpGrowth.findPrefixPath('r',myHeaderTab['r'][1])
Out[6]: {frozenset({'s', 'x'}): 1, frozenset({'z'}): 1, frozenset({'x', 'y', 'z'}): 1}
下面繼續(xù)補(bǔ)充完整程序:
def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1])]#對(duì)頭指針表的元素項(xiàng)按照其出現(xiàn)的頻率進(jìn)行排序
for basePat in bigL: #從頭指針的底端開(kāi)始
newFreqSet = preFix.copy()
newFreqSet.add(basePat)
freqItemList.append(newFreqSet)
condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
myCondTree, myHead = createTree(condPattBases, minSup)
if myHead != None: #遞歸調(diào)用
print 'conditional tree for: ',newFreqSet
myCondTree.disp(1)
mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)
接下來(lái)運(yùn)行mineTree(),顯示出所有的條件樹(shù):
In [14]: import fpGrowth
...: freqItems = []
...: fpGrowth.mineTree(myFPtree, myHeaderTab, 3, set([]),freqItems)
...:
conditional tree for: set(['y'])
Null Set 1
x 3
z 3
conditional tree for: set(['y', 'z'])
Null Set 1
x 3
conditional tree for: set(['s'])
Null Set 1
x 3
conditional tree for: set(['t'])
Null Set 1
y 3
x 3
z 3
conditional tree for: set(['z', 't'])
Null Set 1
y 3
x 3
conditional tree for: set(['x', 'z', 't'])
Null Set 1
y 3
conditional tree for: set(['x', 't'])
Null Set 1
y 3
conditional tree for: set(['x'])
Null Set 1
z 3
下面檢查一下返回的項(xiàng)集是否與條件樹(shù)匹配:
In [15]: freqItems
Out[15]:
[{'y'},
{'y', 'z'},
{'x', 'y', 'z'},
{'x', 'y'},
{'s'},
{'s', 'x'},
{'t'},
{'t', 'z'},
{'t', 'y', 'z'},
{'t', 'x', 'z'},
{'t', 'x', 'y', 'z'},
{'t', 'x'},
{'t', 'x', 'y'},
{'t', 'y'},
{'r'},
{'x'},
{'x', 'z'},
{'z'}]