Frequent Pattern 資料探勘關聯規則演算法(Aprior演算法) FT-Tree

hunhun1122發表於2018-03-26

定義

何謂頻繁模式挖掘呢?所謂頻繁模式指的是在樣本資料集中頻繁出現的模式。舉個例子,比如在超市的交易系統中,記載了很多次交易,每一次交易的資訊包括使用者購買的商品清單。如果超市主管是個有心人的話,他會發現尿不溼,啤酒這兩樣商品在許多使用者的購物清單上都出現了,而且頻率非常高。尿不溼,啤酒同時出現在一張購物單上就可以稱之為一種頻繁模式,這樣的發掘就可以稱之為頻繁模式挖掘。這樣的挖掘是非常有意義的,上述的例子就是在沃爾瑪超市發生的真例項子,至今為工業界所津津樂道。

Aprior挖掘演算法:

那麼接下來的問題就很自然了,使用者該如何有效的挖掘出所有這樣的模式呢?下面我們就來討論一下最簡單,最自然的一種方法。在談到這個演算法之前,我們先宣告一個在頻繁模式挖掘中的一個特性-Aprior特性。

Aprior特性:

這個特性是指如果一個Item set(專案集合)不是frequent item set(頻繁集合),那麼任何包含它的專案集合一定也不是頻繁集合.這裡的集合就是模式.這個特性很自然,也很容易理解.比如還是看上面沃爾瑪超市的例子,如果啤酒這個商品在所有的購物清單中只出現過1次,那麼任何包含啤酒這個商品的購物商品組合,比如(啤酒,尿不溼)最多也只出現了一次,如果我們認定出現次數多於2次的專案集合才能稱之為頻繁集合的話,那麼這些包含了啤酒的購物組合肯定都不是頻繁集合.反之,如果一個專案集合是頻繁集合,那麼它的任意非空子集也是頻繁集合.

有了這個特性,那麼就可以在挖掘過程中對一些不可能的專案集合進行排除,避免造成不必要的計算浪費.這個方法主要包含兩個操作:product(叉積)和prune(剪枝).這兩種操作是整個方法的核心.

首先是product:

先有幾個定義,L(k)-候選專案佇列,該佇列中包含一系列的專案集合(也就是說佇列是專案集合的集合),這些專案集合的長度都是一樣的,都為k,這個長度我們稱之為秩(呵呵,秩是我自己取的中文名),這些長度相同的集合稱之為k-集合。那麼就有L(k+1)=L(k) product L(k).也就是說通過product操作(自叉積),秩為k的候選佇列可以生成秩為k+1的候選佇列。需要注意的是,這裡所有的候選佇列中的k-集合都按照字母順序(或者是另外的某種事先定義好的順序)排好序了。好,下面關鍵來了,product該如何執行?product操作是針對候選佇列中的k-集合的,實際上就是候選佇列中的k-集合兩兩進行執行join操作。K-集合l1,l2之間能夠進行join有一個前提,那就是兩個k-集合的前k-1個專案是相同的,並且l1(k)的順序大於l2(k)(這個順序的要求是為了排除重複結果)。用公式表示這個前提就是(l1[1]=l2[1])^(l1[2]=l2[2])^…^(l1[k-1]=l2[k-1])^(l1[k]<l2[k]).那麼join的結果就形成了一個k+1長度的集合l1[1],l1[2],…,l1[k-1],l1[k],l2[k]。如果L(k)佇列中的所有k-集合兩兩之間都完成了join操作,那麼這些形成的k+1長度的集合就構成了一個新的秩為k+1的候選專案佇列L(k+1)。

剪枝操作:

這個操作是針對候選佇列的,它對候選佇列中的所有k-集合進行一次篩選,篩選過程會對資料庫進行一次掃描,把那些不是頻繁專案集合的k-集合從L(k)候選佇列中去掉。為什麼這麼做呢?還記得前面提到的Aprior特性麼?因為這些不是頻繁集合的k-集合通過product操作無法生成頻繁集合,它對product操作產生頻繁集合沒有任何貢獻,把它保留在候選佇列中除了增加複雜度沒有任何其他優點,因此就把它從佇列中去掉。

這兩個操作就構成了演算法的核心,使用者從秩為1的專案候選佇列開始,通過product操作,剪枝操作生成秩為2的候選佇列,再通過同樣的2步操作生成秩為3的候選佇列,一直迴圈操作,直到候選佇列中所有的k-集合的出現此為等於support count.


FP樹構造

FP Growth演算法利用了巧妙的資料結構,大大降低了Aproir挖掘演算法的代價,他不需要不斷得生成候選專案佇列和不斷得掃描整個資料庫進行比對。為了達到這樣的效果,它採用了一種簡潔的資料結構,叫做frequent-pattern tree(頻繁模式樹)。下面就詳細談談如何構造這個樹,舉例是最好的方法。請看下面這個例子:

 

這張表描述了一張商品交易清單,abcdefg代表商品,(ordered)frequent items這一列是把商品按照降序重新進行了排列,這個排序很重要,我們操作的所有專案必須按照這個順序來,這個順序的確定非常簡單,只要對資料庫進行一次掃描就可以得到這個順序。由於那些非頻繁的專案在整個挖掘中不起任何作用,因此在這一列中排除了這些非頻繁專案。我們在這個例子中設定最小支援閾值(minimum support threshold)為3。

我們的目標是為整個商品交易清單構造一顆樹。我們首先定義這顆樹的根節點為null,然後我們開始掃描整個資料庫的每一條記錄開始構造FP樹。

第一步:掃描資料庫的第一個交易,也就是TID為100的交易。那麼就會得到這顆樹的第一個分支<(f:1),(c:1),(a:1),(m:1),(p:1)>。注意這個分支一定是要按照降頻排列的。


 

第二步:掃描第二條交易記錄(TID=200),我們會有這麼一個頻繁專案集合<f,c,a,b,m>。仔細觀察這個佇列,你會發現這個集合的前3項<f,c,a>與第一步產生的路徑<f,c,a,m,p>的前三項是相同的,也就是說他們可以共享一個字首。於是我們在第一步產生的路徑的基礎上,把<f,c,a>三個節點的數目加1,然後將<(b:1),(m:1)>作為一個分支加在(a:2)節點的後面,成為它的子節點。看下圖

第三步:接著掃描第三條交易記錄(TID=300),你會看到這條記錄的集合是<f, b>,與已存在的路徑相比,只有f是共有的字首,那麼f節點加1,同時再為f節點生成一個新的位元組點(b:1).就會有下圖:

 

第四步:繼續看第四條交易記錄,它的集合是<c,b,p>,哦,這回不一樣了。你會發現這個集合的第一個元素是c,與現存的已知路徑的第一個節點f不一樣,那就不用往下比了,沒有任何公共字首。直接將該集合作為根節點的子路徑附加上去。就得到了下圖(圖1):

第五步:最後一條交易記錄來了,你看到了一條集合<f,c,a,m,p>。你驚喜得發現這條路徑和樹現有最左邊的路徑竟然完全一樣。那麼,這整條路徑都是公共字首,那麼這條路徑上的所有點都加1好了。就得到了最終的圖(圖2)。

好了,一顆FP樹就已經基本構建完成了。等等,還差一點。上述的樹還差一點點就可以稱之為一個完整的FP樹啦。為了便於後邊的樹的遍歷,我們為這棵樹又增加了一個結構-頭表,頭表儲存了所有的頻繁專案,並且按照頻率的降序排列,表中的每個專案包含一個節點連結串列,指向樹中和它同名的節點。羅嗦了半天,可能還是不清楚,好吧直接上圖,一看你就明白:

以上就是整個FP樹構造的完整過程。聰明的讀者一定不難根據上述例子歸納總結出FP樹的構造演算法。這裡就不再贅述。詳細的演算法參考文獻1。

 

FP樹的挖掘

 

下面就是最關鍵的了。我們已經有了一個非常簡潔的資料結構,下一步的任務就是從這棵樹裡挖掘出我們所需要的頻繁專案集合而不需要再訪問資料庫了。還是看上面的例子。

 

第一步:我們的挖掘從頭表的最後一項p開始,那麼一個明顯的直接頻繁集是(p:3)了。根據p的節點連結串列,它的2個節點存在於2條路徑當中:路徑<f:4,c:3,a:3,m:2,p:2>和路徑<c:1,b:1,p:1>.從路徑<f:4,c:3,a:3,m:2,p:2>我們可以看出包含p的路徑<f,c,a,m,p>出現了2次,同時也會有<f,c,a>出現了3次,<f>出現了4次。但是我們只關注<f,c,a,m,p>,因為我們的目的是找出包含p的所有頻繁集合。同樣的道理我們可以得出<c,b,p>在資料庫中出現了1次。於是,p就有2個字首路徑{(fcam:2),(cb:1)}。這兩條字首路徑稱之為p的子模式基(subpattern-base),也叫做p的條件模式基(之所以稱之為條件模式基是因為這個子模式基是在p存在的前提條件下)。接下來我們再為這個條件子模式基構造一個p的條件FP樹。再回憶一下上面FP樹的構造演算法,很容易得到下面這棵樹:

 

但是由於頻繁集的閾值是3。那麼實際上這棵樹經過剪枝之後只剩下一個分支(c:3),所以從這棵條件FP樹上只能派生出一個頻繁專案集{cp:3}.加上直接頻繁集(p:3)就是最後的結果.

第二步:我們接下來開始挖掘頭表中的倒數第二項m,同第一步一樣,顯然有一個直接的頻繁集(m:3).再檢視它在FP樹中存在的兩條路徑<f:4,c:3,a:3,m:2>和<f:4,c:3,a:3,b1,m:1>.那麼它的頻繁條件子模式基就是{ (fca:2),(fcab:1)}.為這個子模式基構造FP樹,同時捨棄不滿足最小頻繁閾值的分支b,那麼其實在這棵FP樹中只存在唯一的一個頻繁路徑<f:3,c:3,a:3>.既然這顆子FP樹是存在的,並且不是一顆只有一個節點的特殊的樹,我們就繼續遞迴得挖掘這棵樹.這棵子樹是單路徑的子樹,我們可以簡化寫成mine(FP tree|m)=mine(<f:3,c:3,a:3>|m:3).

下面來闡述如何挖掘這顆FP子樹,我們需要遞迴.遞迴子樹也需要這麼幾個步驟:

1這顆FP子樹的頭表最後一個節點是a,結合遞迴前的節點m,那麼我們就得到am的條件子模式基{(fc:3)},那麼此子模式基構造的FP樹(我們稱之為m的子子樹)實際上也是一顆單路徑的樹<f:3,c:3>,接下也繼續繼續遞迴挖掘子子樹mine(<f:3,c:3>|am:3). (子子樹的遞迴分析暫時打住.因為再分析子子樹的遞迴的話文字就會顯得太混亂)

2同樣,FP子樹頭表的倒數第二個節點是c,結合遞迴前節點m,就有我們需要遞迴挖掘mine(<f:3>|cm:3).

3 FP子樹的倒數第三個節點也是最後一個節點是f,結合遞迴前的m節點,實際上需要遞迴挖掘mine(null|fm:3),實際上呢這種情況下的遞迴就可以終止了,因為子樹已經為空了.因此此情況下就可以返回頻繁集合<fm:3>

注意:這三步其實還包含了它們直接的頻繁子模式<am:3>,<cm:3>,<fm:3>,這在每一步遞迴呼叫mine<FPtree>都是一樣的,就不再羅嗦得一一重新指明瞭.

實際上這就是一個很簡單的遞迴過程,就不繼續往下分析了,聰明的讀者一定會根據上面的分析繼續往下推導遞迴,就會得到下面的結果.

mine(<f:3,c:3>|am:3)=><cam:3>,<fam:3>,<fcam:3>

mine(<f:3>|cm:3)=><fcm:3>

mine(null|fm:3)=><fm:3>

這三步還都包含了各自直接的頻繁子模式<am:3>,<cm:3>,<fm:3>.

最後再加上m的直接頻繁子模式<m:3>,就是整個第二步挖掘m的最後的結果。請看下圖

 

第三步:來看看頭表倒數第三位<b:3>的挖掘,它有三條路徑<f:4,c:3,a:3,b:1>,<f:4,b:1>,<c:1,b:1>,形成的頻繁條件子模式基為{(fca:1),(f:1),(c:1)},構建成的FP樹中的所有節點的頻率均小於3,那麼FP樹為空,結束遞迴.這一步得到的頻繁集就只有直接頻繁集合<b:3>

第四步:頭表倒數第四位<a:3>,它有一條路徑<f:4,c:3>,頻繁條件子模式基為{(fc:3)},構成一個單路徑的FP樹.實際上可能有人早已經發現了,這種單路徑的FP樹挖掘其實根本不用遞迴這麼麻煩,只要進行排列組合就可以直接組成最後的結果.實際上也確實如此.那麼這一步最後的結果根據排列組合就有:{(fa:3),(ca:3),(fca:3),(a:3)}

第五步:頭表的倒數第五位<c:4>,它只有一條路徑<f:4>,頻繁條件子模式基為{(f:3)},那麼這一步的頻繁集也就很明顯了:{(fc:3),(c:4)}

第六步:頭表的最後一位<f:4>,沒有條件子模式基,那麼只有一個直接頻繁集{(f:4)}

這6步的結果加在一起,就得到我們所需要的所有頻繁集.下圖給出了每一步頻繁條件模式基.

其實,通過上面的例子,估計早有人看出來了,這種單路徑的FP樹挖掘其實是有規律的,根本不用遞迴這麼複雜的方法,通過排列組合可以直接生成.的確如此,Han Jiawei針對這種單路徑的情況作了優化.如果一顆FP樹有一個很長的單路徑,我們將這棵FP樹分成兩個子樹:一個子樹是由原FP樹的單路徑部分組成,另外一顆子樹由原FP樹的除單路徑之外的其餘部分組成.對這兩個子樹分別進行FP Growth演算法,然後對最後的結果進行組合就可可以了.

通過上面博主不厭其煩,孜孜不倦,略顯羅嗦的分析,相信大家已經知道FP Growth演算法的最終奧義.實際上該演算法的背後的思想很簡單,用一個簡潔的資料結構把整個資料庫進行FP挖掘所需要的資訊都包含了進去,通過對資料結構的遞迴就可以完成整個頻繁模式的挖掘.由於這個資料結構的size遠遠小於資料庫,因此可以儲存在記憶體中,那麼挖掘速度就可以大大提高.

也許有人會問?如果這個資料庫足夠大,以至於構造的FP樹大到無法完全儲存在記憶體中,這該如何是好.這的確是個問題. Han Jiawei在論文中也給出了一種思路,就是通過將原來的大的資料庫分割槽成幾個小的資料庫(這種小的資料庫稱之為投射資料庫),對這幾個小的資料庫分別進行FP Growth演算法.

還是拿上面的例子來說事,我們把包含p的所有資料庫記錄都單獨存成一個資料庫,我們稱之為p-投射資料庫,類似的m,b,a,c,f我們都可以生成相應的投射資料庫,這些投射資料庫構成的FP樹相對而言大小就小得多,完全可以放在記憶體裡.

在現代資料探勘任務中,資料量越來越大,因此並行化的需求越來越大,上面提出的問題也越來越迫切.下一篇部落格,博主將分析一下,FP Growth如何在MapReduce的框架下並行化.

[1]Mining Frequent Patterns  without Candidate Generation: AFrequent-Pattern Tree Approach

相關文章