對FP-Growth算法構建原理、條件模式基的理解

在學習關聯規則挖掘的時候,查閱了一些相關的資料。大多數博客都比較相似,介紹每一步的算法流程。我也就是把我自己學習算法過程中的一些問題和對問題的理解記錄一下,希望可以加深大家對該算法的理解。

本篇可能不會詳細講解FP樹的構建過程或是算法流程,而是會對算法中的一些關鍵點給出自己的理解,分析算法爲什麼要這麼做,這麼做的目的是什麼。從而可以加深一些對算法的深入理解。同時也需要對Apriori算法有個大致的理解,才能進行對比理解到爲什麼要建FP樹的原因。

**

FP算法簡介

**
FP-growth算法將數據存儲在一種稱爲FP樹的緊湊數據結構中。FP代表頻繁模式(Frequent Pattern)。一棵FP樹看上去與計算機科學中的其他樹結構類似,但是它通過鏈接(Link)來連接相似元素,被連起來的元素項可以看成一個鏈表。

如圖給出一個FP樹的例子(該例子與Machine Learning in Action 一致)

原始數據集爲,及書本上過濾及重排序後的事務爲:
在這裏插入圖片描述
根據該數據集生成的FP樹爲:
一棵FP樹,看上去和一般的樹沒什麼兩樣,包含着連接相似節點的連接
FP樹會存儲項集的出現頻率,而每個項集會以路徑的方式存儲在樹中。存在相似元素的集合會共享樹的一部分。只有當集合之間完全不同時,樹纔會分叉。樹節點上給出集合中的單個元素及其在序列中的出現次數,路徑會給出該序列的出現次數。

相似項之間的鏈接即節點鏈接(node link),用於快速發現相似項的位置。

於是我們可以對FP樹有一個大致的認識:
首先需要設定一個頭指針表來指向給定類型的第一個實例
利用頭指針表可以快速訪問FP樹種的一個給定類型的所有元素

FP樹遍歷兩次數據集:
1-統計每個元素項出現的頻率
2-遍歷每個記錄來構建FP樹,讀入每個項集並將其添加到一條已經存在的路徑中

在此之前,我們先對以上構建FP樹中的一些思想進行總結。構建FP樹的思想即是:

我們知道由於Apriori算法在找到某一頻繁項集後,都要遍歷一遍原始數據集來查找該項集在數據集中出現的次數。這就導致了每個項集都要對原始數據集全部掃描一下,這樣在數據量較大時是非常麻煩的。這也是Apriori算法最大的弊端。那我們想要對此進行改進,改進的算法即是想如何可以更高效的確定某一項集在原始數據集中出現的次數

由此想到了可以利用樹形結構來表示,爲什麼呢?

因爲樹的每一條分支可以代表一條路徑,即從根節點到某一節點之間出現的所有節點。於是我們可以對原始數據集進行合併修整,對有公共前綴的記錄進行整合,並利用節點的數值標明,從根節點到該節點之間所有元素同時出現的次數。這樣對於某一目標節點,我們就可以通過遍歷其父節點(找到前綴路徑)來快速找到與其共現的元素集合。

我們可以對FP樹的作用總結如下:
FP樹相當於對原始數據集的整合,構建FP樹的目的是爲了可以快速的發現與目標元素共現的元素集合,而不是在海量的數據中利用全掃描來盲目的尋找。根下一節在條件模式基下建FP樹的聯繫其實就是,例如對於上圖中的headerTable表{z,r,x,y,s,t},在Apriori算法中,如果我們希望找兩項集,我們想要對其中的元素兩兩合併比如{z,r},{z,x},…{x,y}…{s,t}然後分別把每個都去原始數據集中查找看出現次數,這種是很費時間的,因爲有些項可能根本就不會共現。所以構建FP樹的目的就是把共現過的元素放在同一路徑上表示,這樣對於某一元素,我們不需要把它跟其他所有剩餘的元素都連接,而只用把它跟它共現過的元素進行拼接,這也就是在條件模式基下找元素前綴路徑的作用,只要在前綴路徑中的元素,纔會與目標元素連接,這樣就可以不用做無用功,改進了Apriori算法的弊端。

利用條件模式基找頻繁項集

我們的算法最終目的,還是爲了去挖掘頻繁項集。接下來便討論FP-Growth算法中更爲重要的思想——利用條件模式基去挖掘頻繁項集。

上文已經理解了FP樹的作用,其實我感覺FP樹不是爲了找頻繁項集而產生的,它的目的更多的是在對原始數據的處理上,是爲了方便我們在找頻繁項集時,可以專注於那些與目標元素(項集)共現過的元素集合,而不用考慮那些可能並不會共現的元素。

接下來我們討論一下headerTable的作用。headerTable表中不僅保存了元素的出現次數,還有該元素在樹中的第一個元素節點。
之前講了那麼多,說FP樹可以找到與目標元素共現過的元素的頻繁項集等等。那還有一個問題就是,當找到了某一元素的頻繁項集,如果不去掃描原始數據集,如何得知該頻繁項集的出現次數呢?
我覺得這個點就在headerTable裏。有人會說不是應該在FP樹的節點後面的數字表示嗎?我的理解傾向於FP樹中節點上的數字,更多代表的只是說這某路徑出現的次數,根本還是對數據進行的刻畫。比如在後麪條件模式基中找前綴路徑時,根據節點的計數我們就可以得知某條前綴路徑的出現次數,從而可以對某一單個元素進行計數求和(也就是得出headerTable表中某一元素的全部出現次數)。

那爲什麼是在headerTable表中呢?因爲headerTable表保存了某一元素出現的全部次數。在構建第一棵FP樹時,我們可以假定headerTable表中保存的是“當前提條件爲空時,各元素的出現次數”。現在我們假設希望尋找元素t的多項集,
同樣以上圖中的樹爲例。

1.按照上文所說,我們需要先從樹中找出前綴路徑,從而找出與t共現過的元素集合:

t:{z,x,y,s}:2, {z,x,y,r}:1

2.接着統計這兩天記錄的次數,構建headerTable表(這裏只是爲了從邏輯層面來理解,先不考慮表中保存的鏈接節點)。可以得出headerTable表中各元素出現的次數爲:

headerTable: {z:3, x:3, y:3, s:2, r:1}

這即是表示“當t元素出現的前提下,z,x,y,s,r各元素出現的概率”。那麼我們是否可以得出項集{t,z}的出現次數是3,{t,x}的出現次數是3,{t,s}的出現次數是2呢?
我覺得是可以的。因爲對目標元素找前綴路徑時我們知道,該路徑上的元素即是與目標元素共現過的元素集合。那麼當我們將在多條路徑上出現了的目標元素找到其前綴路徑後,就相當於找出了在原始數據集中包含該目標元素(或元素項)的記錄。既然這些記錄中包含了目標元素,那麼必然目標元素可以與前綴路徑中的元素兩兩組合,且其數值等於對前綴路徑中各元素整合求和後的計數值,也就是headerTable中各元素的數值。

3.我們在找完t的兩項集後,希望繼續去挖掘三項集。怎麼找呢?同樣的是按之前的思想。如果是按Apriori那就是對{z,x,y,s,r}兩兩組合,那麼FP算法就是與2步驟同樣的思想,我們希望在連接前,先找到與目標元素共現過的元素集合,例如我們希望先找到z是該與x連接成{t,z,x}還是與y組成{t,z,y}。這就需要我們根據1.中的兩條記錄,繼續構造FP樹,如下:(所以我覺得FP樹就是用來找與目標元素共現過的元素集合,從而避免了Apriori算法中的直接兩兩連接):
t出現前提下,構建的條件FP樹結構
例如上圖根據條件FP樹我們可以看出不存在三項集{t,s,r},(回顧原始數據集的確沒有),於是我們在搜索三項集時,就不用對該項集進行查找。

以上雖然沒有講到什麼是條件模式基,但是其實大體的思路已經呈現出來了。拿什麼是條件模式基呢?
條件模式基概念解釋是以所查元素項爲結尾的路徑集合,每條路徑其實都是一條前綴路徑。其實也就是我們上文步驟1.所找到的目標元素的兩條前綴路徑。而抽取條件模式基的目的,就是我們上文說的找到目標元素的共現元素集合,從而使目標元素只與共現過的元素進行拼接組合。

條件FP樹也即爲上圖中對目標元素r構建的條件FP樹。條件樹的作用也是爲了該樹對應的headerTable表中的元素進行拼接時,可以使得元素只與自己共現過的元素進行拼接。

總結

於是可以對以上總結一下各點的思路:
1.構造FP樹是將原始數據進行合併整合,將共現過的元素放在樹的同一分支下,這樣可以快速找到與目標元素共現過的元素集合
2.尋找前向路徑即是尋找與目標元素共現過的元素集合,這樣避免了Apriori算法中需要把目標元素與剩餘所有元素進行拼接組合
3.創建條件模式基的過程即是去發現雙項集、三項集、四項集等的過程。
每個條件模式基中的headerTable表中保存的元素的計數,即表示了在以目標元素爲條件出現的前提下,該元素出現的次數。
例如以目標元素{r}爲條件出現的前提下,構造的headerTable表中的元素計數爲{z:2,x:2,s:1,y:1,t:1}
即表示當r出現的前提下,z出現的次數爲2。相當於{r,z}的二項集的次數爲2,{r,t}二項集的次數爲1。以此類推

那既然headerTable表中已經存放了生產二項集、三項集的次數,爲什麼還要建立對應的樹呢?
又回到上文提到的對建樹的理解。其實建樹只是對原始數據的一種轉換,把數據進行整合,有相同前綴的數據記錄可以放到FP樹的 一條分支上去。使得我們可以不用每次都在海量的原始數據中搜索,而是可以直接關注於與目標元素共現過的那些元素。

同樣的,如果我們不建立模式基下的FP樹,則我們在知道r的雙項集{r,z},{r,x}…之後,我們需要找三項集的時候,還是需要對headerTable表中的所有元素進行兩兩組合。而有了模式基下的樹後,我們可以快速找到哪些是與目標元素共現過的元素
比如對{r,z}找模式基發現z的前綴路徑只有元素{x},於是可以很快得出在{r,z}出現的前提下,只有{r,z,x}, 而不會有{r,z,s}出現

所以再總結一下就是headerTable表用來計數,FP樹用來找共現元素集合,以此類推不斷找二項集、三項集等

最後提一點,書中所給出的代碼中,只最後用列表保存了頻繁項集,而沒有給出對應頻繁項集出現的次數。我也是根據我以上的理解,自己添加了代碼增加了某一頻繁項集的出現次數,代碼如下:

def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
    bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p:p[1][0])]		#這裏代碼書中感覺也給錯了
    print('bigL: ',bigL)
    print(inTree.printTree())
    for basePat in bigL:
        retDict = {}
        print('basePat: ', basePat)
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        print('newFreqSet: ', newFreqSet)
        retDict[frozenset(newFreqSet)] = headerTable[basePat][0]		#這裏是添加項集出現次數的代碼,以字典的形式保存
        #freqItemList.append(newFreqSet)
        freqItemList.append(retDict)
        condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
        print('condPattBases: ', condPattBases)
        myCondTree, myHead = createTree(condPattBases, minSup)

        print('head from conditional tree: ', myHead)
        if myHead != None:
            mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)

以上是我先學習FP-Growth算法過程中,產生的一些思考以及自己的理解。我一開始不太清除FP樹的作用,書中講解條件模式基和條件樹的部分一開始也沒有很好的理解清楚,就不斷的自己根據思想,手動把樹畫出來然後一步一步對着算法流程進行分析。以上很多地方表達的不是特別清楚,也希望大家有什麼不一樣的想法都可以進行探討討論。感謝大家抽空看我吹了這麼多的水,希望能起到一點幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章