前面我們講到頻繁項集挖掘的關聯算法Apriori和FP Tree。這兩個算法都是挖掘頻繁項集的。而今天我們要介紹的PrefixSpan算法也是關聯算法,但是它是挖掘頻繁序列模式的,因此要解決的問題目標稍有不同。
1. 項集數據和序列數據
首先我們看看項集數據和序列數據有什么不同,如下圖所示。

左邊的數據集就是項集數據,在Apriori和FP Tree算法中我們也已經看到過了,每個項集數據由若干項組成,這些項沒有時間上的先后關系。而右邊的序列數據則不一樣,它是由若干數據項集組成的序列。比如第一個序列<a(abc)(ac)d(cf)>,它由a,abc,ac,d,cf共5個項集數據組成,並且這些項有時間上的先后關系。對於多於一個項的項集我們要加上括號,以便和其他的項集分開。同時由於項集內部是不區分先后順序的,為了方便數據處理,我們一般將序列數據內所有的項集內部按字母順序排序。
2. 子序列與頻繁序列
了解了序列數據的概念,我們再來看看上面是子序列。子序列和我們數學上的子集的概念很類似,也就是說,如果某個序列A所有的項集在序列B中的項集都可以找到,則A就是B的子序列。當然,如果用嚴格的數學描述,子序列是這樣的:
對於序列A={$a_1,a_2,...a_n$}和序列B={$b_1,b_2,...b_m$},$n \leq m$,如果存在數字序列$1 \leq j_1 \leq j_2 \leq ... \leq j_n \leq m$, 滿足$a_1 \subseteq b_{j_1}, a_2 \subseteq b_{j_2}...a_n \subseteq b_{j_n} $,則稱A是B的子序列。當然反過來說, B就是A的超序列。
而頻繁序列則和我們的頻繁項集很類似,也就是頻繁出現的子序列。比如對於下圖,支持度閾值定義為50%,也就是需要出現兩次的子序列才是頻繁序列。而子序列<(ab)c>是頻繁序列,因為它是圖中的第一條數據和第三條序列數據的子序列,對應的位置用藍色標示。

3. PrefixSpan算法的一些概念
PrefixSpan算法的全稱是Prefix-Projected Pattern Growth,即前綴投影的模式挖掘。里面有前綴和投影兩個詞。那么我們首先看看什么是PrefixSpan算法中的前綴prefix。
在PrefixSpan算法中的前綴prefix通俗意義講就是序列數據前面部分的子序列。比如對於序列數據B=<a(abc)(ac)d(cf)>,而A=<a(abc)a>,則A是B的前綴。當然B的前綴不止一個,比如<a>, <aa>, <a(ab)> 也都是B的前綴。
看了前綴,我們再來看前綴投影,其實前綴投影這兒就是我們的后綴,有前綴就有后綴嘛。前綴加上后綴就可以構成一個我們的序列。下面給出前綴和后綴的例子。對於某一個前綴,序列里前綴后面剩下的子序列即為我們的后綴。如果前綴最后的項是項集的一部分,則用一個“_”來占位表示。
下面這個例子展示了序列<a(abc)(ac)d(cf)>的一些前綴和后綴,還是比較直觀的。要注意的是,如果前綴的末尾不是一個完全的項集,則需要加一個占位符。
在PrefixSpan算法中,相同前綴對應的所有后綴的結合我們稱為前綴對應的投影數據庫。

4. PrefixSpan算法思想
現在我們來看看PrefixSpan算法的思想,PrefixSpan算法的目標是挖掘出滿足最小支持度的頻繁序列。那么怎么去挖掘出所有滿足要求的頻繁序列呢。回憶Aprior算法,它是從頻繁1項集出發,一步步的挖掘2項集,直到最大的K項集。PrefixSpan算法也類似,它從長度為1的前綴開始挖掘序列模式,搜索對應的投影數據庫得到長度為1的前綴對應的頻繁序列,然后遞歸的挖掘長度為2的前綴所對應的頻繁序列,。。。以此類推,一直遞歸到不能挖掘到更長的前綴挖掘為止。
比如對應於我們第二節的例子,支持度閾值為50%。里面長度為1的前綴包括<a>, <b>, <c>, <d>, <e>, <f>,<g>我們需要對這6個前綴分別遞歸搜索找各個前綴對應的頻繁序列。如下圖所示,每個前綴對應的后綴也標出來了。由於g只在序列4出現,支持度計數只有1,因此無法繼續挖掘。我們的長度為1的頻繁序列為<a>, <b>, <c>, <d>, <e>,<f>。去除所有序列中的g,即第4條記錄變成<e(af)cbc>

現在我們開始挖掘頻繁序列,分別從長度為1的頻繁項開始。這里我們以d為例子來遞歸挖掘,其他的節點遞歸挖掘方法和D一樣。方法如下圖,首先我們對d的后綴進行計數,得到{a:1, b:2, c:3, d:0, e:1, f:1,_f:1}。注意f和_f是不一樣的,因為前者是在和前綴d不同的項集,而后者是和前綴d同項集。由於此時a,d,e,f,_f都達不到支持度閾值,因此我們遞歸得到的前綴為d的2項頻繁序列為<db>和<dc>。接着我們分別遞歸db和dc為前綴所對應的投影序列。首先看db前綴,此時對應的投影后綴只有<_c(ae)>,此時_c,a,e支持度均達不到閾值,因此無法找到以db為前綴的頻繁序列。現在我們來遞歸另外一個前綴dc。以dc為前綴的投影序列為<_f>, <(bc)(ae)>, <b>,此時我們進行支持度計數,結果為{b:2, a:1, c:1, e:1, _f:1},只有b滿足支持度閾值,因此我們得到前綴為dc的三項頻繁序列為<dcb>。我們繼續遞歸以<dcb>為前綴的頻繁序列。由於前綴<dcb>對應的投影序列<(_c)ae>支持度全部不達標,因此不能產生4項頻繁序列。至此以d為前綴的頻繁序列挖掘結束,產生的頻繁序列為<d><db><dc><dcb>。
同樣的方法可以得到其他以<a>, <b>, <c>, <e>, <f>為前綴的頻繁序列。

5. PrefixSpan算法流程
下面我們對PrefixSpan算法的流程做一個歸納總結。
輸入:序列數據集S和支持度閾值$\alpha$
輸出:所有滿足支持度要求的頻繁序列集
1)找出所有長度為1的前綴和對應的投影數據庫
2)對長度為1的前綴進行計數,將支持度低於閾值$\alpha$的前綴對應的項從數據集S刪除,同時得到所有的頻繁1項序列,i=1.
3)對於每個長度為i滿足支持度要求的前綴進行遞歸挖掘:
a) 找出前綴所對應的投影數據庫。如果投影數據庫為空,則遞歸返回。
b) 統計對應投影數據庫中各項的支持度計數。如果所有項的支持度計數都低於閾值$\alpha$,則遞歸返回。
c) 將滿足支持度計數的各個單項和當前的前綴進行合並,得到若干新的前綴。
d) 令i=i+1,前綴為合並單項后的各個前綴,分別遞歸執行第3步。
6. PrefixSpan算法小結
PrefixSpan算法由於不用產生候選序列,且投影數據庫縮小的很快,內存消耗比較穩定,作頻繁序列模式挖掘的時候效果很高。比起其他的序列挖掘算法比如GSP,FreeSpan有較大優勢,因此是在生產環境常用的算法。
PrefixSpan運行時最大的消耗在遞歸的構造投影數據庫。如果序列數據集較大,項數種類較多時,算法運行速度會有明顯下降。因此有一些PrefixSpan的改進版算法都是在優化構造投影數據庫這一塊。比如使用偽投影計數。
當然使用大數據平台的分布式計算能力也是加快PrefixSpan運行速度一個好辦法。比如Spark的MLlib就內置了PrefixSpan算法。
不過scikit-learn始終不太重視關聯算法,一直都不包括這一塊的算法集成,這就有點落伍了。
(歡迎轉載,轉載請注明出處。歡迎溝通交流: liujianping-ok@163.com)
