Aprior算法和FP Group算法

轉自 http://blog.sina.com.cn/s/articlelist_1761593252_1_1.html
分別詳細介紹了Aprior算法和FP Group算法。他們的區別就是Apriori算法多次掃描交易數據庫,每次利用候選頻繁集產生頻繁集,而FP則利用樹形結構,不用產生候選頻繁集而是直接得到頻繁集,大大減少了掃描的次數,從而算法效率提高,但是apriori的擴展性好,可以用於並行計算等領域。

數據挖掘中有一個很重要的應用,就是Frequent Pattern挖掘,翻譯成中文就是頻繁模式挖掘。這篇博客就想談談頻繁模式挖掘相關的一些算法。

定義

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

Aprior挖掘算法:

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

Aprior特性:

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

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

首先是product(不少地方也稱此步驟爲合併吧):

先有幾個定義,L(k)-候選項目隊列,該隊列中包含一系列的項目集合(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])and(l1[2]=l2[2])andand(l1[k1]=l2[k1])and(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)。

比如說,候選頻繁2項集{啤酒,尿布},{啤酒,花生}就可以被合併成3項集{啤酒,尿布,花生}

剪枝操作:

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

這兩個操作就構成了算法的核心,用戶從秩爲1的項目候選隊列開始,通過product操作,剪枝操作生成秩爲2的候選隊列,再通過同樣的2步操作生成秩爲3的候選隊列,一直循環操作,直到候選隊列中所有的k-集合的出現此爲等於support count.

下面給出一個具體的例子,可以很好得闡述上面的算法思想:

這裏寫圖片描述

這裏寫圖片描述

這種算法思路比較清晰直接,實施起來比較簡單。但是缺點就是代價很大,每一次剪枝操作都會對數據庫進行掃描,每一次product操作需要對隊列中的k-集合兩兩進行join操作,其複雜度爲C(sizeof(L(k)),2)。

爲了提高算法效率,Han Jiawei提出了FP Growth算法,使得頻繁模式的挖掘效率又提升了一個數量級。

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的框架下並行化.

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