機(jī)器學(xué)習(xí)實(shí)戰(zhàn)-使用FP-growth算法來(lái)高效發(fā)現(xiàn)頻繁項(xiàng)集

上一章介紹了發(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'}]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,104評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,884評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,130評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,366評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,586評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,827評(píng)論 1 283
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,608評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容