用Spark學習FP Tree演算法和PrefixSpan演算法

劉建平Pinard發表於2017-01-22

    在FP Tree演算法原理總結PrefixSpan演算法原理總結中,我們對FP Tree和PrefixSpan這兩種關聯演算法的原理做了總結,這裡就從實踐的角度介紹如何使用這兩個演算法。由於scikit-learn中沒有關聯演算法的類庫,而Spark MLlib有,本文的使用以Spark MLlib作為使用環境。

1. Spark MLlib關聯演算法概述

    在Spark MLlib中,也只實現了兩種關聯演算法,即我們的FP Tree和PrefixSpan,而像Apriori,GSP之類的關聯演算法是沒有的。而這些演算法支援Python,Java,Scala和R的介面。由於前面的實踐篇我們都是基於Python,本文的後面的介紹和使用也會使用MLlib的Python介面。

     Spark MLlib關聯演算法基於Python的介面在pyspark.mllib.fpm包中。FP Tree演算法對應的類是pyspark.mllib.fpm.FPGrowth(以下簡稱FPGrowth類),從Spark1.4開始才有。而PrefixSpan演算法對應的類是pyspark.mllib.fpm.PrefixSpan(以下簡稱PrefixSpan類),從Spark1.6開始才有。因此如果你的學習環境的Spark低於1.6的話,是不能正常的執行下面的例子的。

     Spark MLlib也提供了讀取關聯演算法訓練模型的類,分別是 pyspark.mllib.fpm.FPGrowthModel和pyspark.mllib.fpm.PrefixSpanModel。這兩個類可以把我們之前儲存的FP Tree和PrefixSpan訓練模型讀出來。

2. Spark MLlib關聯演算法引數介紹

    對於FPGrowth類,使用它的訓練函式train主要需要輸入三個引數:資料項集data,支援度閾值minSupport和資料並行執行時的資料分塊數numPartitions。對於支援度閾值minSupport,它的取值大小影響最後的頻繁項集的集合大小,支援度閾值越大,則最後的頻繁項集數目越少,預設值0.3。而資料並行執行時的資料分塊數numPartitions主要在分散式環境的時候有用,如果你是單機Spark,則可以忽略這個引數。

    對於PrefixSpan類, 使用它的訓練函式train主要需要輸入四個引數:序列項集data,支援度閾值minSupport, 最長頻繁序列的長度maxPatternLength 和最大單機投影資料庫的項數maxLocalProjDBSize。支援度閾值minSupport的定義和FPGrowth類類似,唯一差別是閾值預設值為0.1。maxPatternLength限制了最長的頻繁序列的長度,越小則最後的頻繁序列數越少。maxLocalProjDBSize引數是為了保護單機記憶體不被撐爆。如果只是是少量資料的學習,可以忽略這個引數。

    從上面的描述可以看出,使用FP Tree和PrefixSpan演算法沒有什麼門檻。學習的時候可以通過控制支援度閾值minSupport控制頻繁序列的結果。而maxPatternLength可以幫忙PrefixSpan演算法篩除太長的頻繁序列。在分散式的大資料環境下,則需要考慮FPGrowth演算法的資料分塊數numPartitions,以及PrefixSpan演算法的最大單機投影資料庫的項數maxLocalProjDBSize。

3. Spark FP Tree和PrefixSpan演算法使用示例

    這裡我們用一個具體的例子來演示如何使用Spark FP Tree和PrefixSpan演算法挖掘頻繁項集和頻繁序列。

    完整程式碼參見我的github: https://github.com/ljpzzz/machinelearning/blob/master/classic-machine-learning/fp_tree_prefixspan.ipynb

    要使用 Spark 來學習FP Tree和PrefixSpan演算法,首先需要要確保你安裝好了Hadoop和Spark(版本不小於1.6),並設定好了環境變數。一般我們都是在ipython notebook(jupyter notebook)中學習,所以最好把基於notebook的Spark環境搭好。當然不搭notebook的Spark環境也沒有關係,只是每次需要在執行前設定環境變數。

    如果你沒有搭notebook的Spark環境,則需要先跑下面這段程式碼。當然,如果你已經搭好了,則下面這段程式碼不用跑了。

import os
import sys

#下面這些目錄都是你自己機器的Spark安裝目錄和Java安裝目錄
os.environ['SPARK_HOME'] = "C:/Tools/spark-1.6.1-bin-hadoop2.6/"

sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/bin")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python/pyspark")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python/lib")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python/lib/pyspark.zip")
sys.path.append("C:/Tools/spark-1.6.1-bin-hadoop2.6/python/lib/py4j-0.9-src.zip")
sys.path.append("C:/Program Files (x86)/Java/jdk1.8.0_102")

from pyspark import SparkContext
from pyspark import SparkConf


sc = SparkContext("local","testing")

    在跑演算法之前,建議輸出Spark Context如下,如果可以正常列印記憶體地址,則說明Spark的執行環境搞定了。

print sc

    比如我的輸出是:

<pyspark.context.SparkContext object at 0x07D9E2B0>

    現在我們來用資料來跑下FP Tree演算法,為了和FP Tree演算法原理總結中的分析比照,我們使用和原理篇一樣的資料項集,一樣的支援度閾值20%,來訓練資料。程式碼如下:

from  pyspark.mllib.fpm import FPGrowth
data = [["A", "B", "C", "E", "F","O"], ["A", "C", "G"], ["E","I"], ["A", "C","D","E","G"], ["A", "C", "E","G","L"],
       ["E","J"],["A","B","C","E","F","P"],["A","C","D"],["A","C","E","G","M"],["A","C","E","G","N"]]
rdd = sc.parallelize(data, 2)
#支援度閾值為20%
model = FPGrowth.train(rdd, 0.2, 2)

    我們接著來看看頻繁項集的結果,程式碼如下:

sorted(model.freqItemsets().collect())

    輸出即為所有 滿足要求的頻繁項集,大家可以和原理篇裡面分析時產生的頻繁項集比較。程式碼輸出如下:

[FreqItemset(items=[u'A'], freq=8),
 FreqItemset(items=[u'B'], freq=2),
 FreqItemset(items=[u'B', u'A'], freq=2),
 FreqItemset(items=[u'B', u'C'], freq=2),
 FreqItemset(items=[u'B', u'C', u'A'], freq=2),
 FreqItemset(items=[u'B', u'E'], freq=2),
 FreqItemset(items=[u'B', u'E', u'A'], freq=2),
 FreqItemset(items=[u'B', u'E', u'C'], freq=2),
 FreqItemset(items=[u'B', u'E', u'C', u'A'], freq=2),
 FreqItemset(items=[u'C'], freq=8),
 FreqItemset(items=[u'C', u'A'], freq=8),
 FreqItemset(items=[u'D'], freq=2),
 FreqItemset(items=[u'D', u'A'], freq=2),
 FreqItemset(items=[u'D', u'C'], freq=2),
 FreqItemset(items=[u'D', u'C', u'A'], freq=2),
 FreqItemset(items=[u'E'], freq=8),
 FreqItemset(items=[u'E', u'A'], freq=6),
 FreqItemset(items=[u'E', u'C'], freq=6),
 FreqItemset(items=[u'E', u'C', u'A'], freq=6),
 FreqItemset(items=[u'F'], freq=2),
 FreqItemset(items=[u'F', u'A'], freq=2),
 FreqItemset(items=[u'F', u'B'], freq=2),
 FreqItemset(items=[u'F', u'B', u'A'], freq=2),
 FreqItemset(items=[u'F', u'B', u'C'], freq=2),
 FreqItemset(items=[u'F', u'B', u'C', u'A'], freq=2),
 FreqItemset(items=[u'F', u'B', u'E'], freq=2),
 FreqItemset(items=[u'F', u'B', u'E', u'A'], freq=2),
 FreqItemset(items=[u'F', u'B', u'E', u'C'], freq=2),
 FreqItemset(items=[u'F', u'B', u'E', u'C', u'A'], freq=2),
 FreqItemset(items=[u'F', u'C'], freq=2),
 FreqItemset(items=[u'F', u'C', u'A'], freq=2),
 FreqItemset(items=[u'F', u'E'], freq=2),
 FreqItemset(items=[u'F', u'E', u'A'], freq=2),
 FreqItemset(items=[u'F', u'E', u'C'], freq=2),
 FreqItemset(items=[u'F', u'E', u'C', u'A'], freq=2),
 FreqItemset(items=[u'G'], freq=5),
 FreqItemset(items=[u'G', u'A'], freq=5),
 FreqItemset(items=[u'G', u'C'], freq=5),
 FreqItemset(items=[u'G', u'C', u'A'], freq=5),
 FreqItemset(items=[u'G', u'E'], freq=4),
 FreqItemset(items=[u'G', u'E', u'A'], freq=4),
 FreqItemset(items=[u'G', u'E', u'C'], freq=4),
 FreqItemset(items=[u'G', u'E', u'C', u'A'], freq=4)]

    接著我們來看看使用PrefixSpan類來挖掘頻繁序列。為了和PrefixSpan演算法原理總結中的分析比照,我們使用和原理篇一樣的資料項集,一樣的支援度閾值50%,同時將最長頻繁序列程度設定為4,來訓練資料。程式碼如下:

from  pyspark.mllib.fpm import PrefixSpan
data = [
   [['a'],["a", "b", "c"], ["a","c"],["d"],["c", "f"]],
   [["a","d"], ["c"],["b", "c"], ["a", "e"]],
   [["e", "f"], ["a", "b"], ["d","f"],["c"],["b"]],
   [["e"], ["g"],["a", "f"],["c"],["b"],["c"]]
   ]
rdd = sc.parallelize(data, 2)
model = PrefixSpan.train(rdd, 0.5,4)

   我們接著來看看頻繁序列的結果,程式碼如下: 

sorted(model.freqSequences().collect())

   輸出即為所有滿足要求的頻繁序列,大家可以和原理篇裡面分析時產生的頻繁序列比較。程式碼輸出如下: 

[FreqSequence(sequence=[[u'a']], freq=4),
 FreqSequence(sequence=[[u'a'], [u'a']], freq=2),
 FreqSequence(sequence=[[u'a'], [u'b']], freq=4),
 FreqSequence(sequence=[[u'a'], [u'b'], [u'a']], freq=2),
 FreqSequence(sequence=[[u'a'], [u'b'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'a'], [u'b', u'c']], freq=2),
 FreqSequence(sequence=[[u'a'], [u'b', u'c'], [u'a']], freq=2),
 FreqSequence(sequence=[[u'a'], [u'c']], freq=4),
 FreqSequence(sequence=[[u'a'], [u'c'], [u'a']], freq=2),
 FreqSequence(sequence=[[u'a'], [u'c'], [u'b']], freq=3),
 FreqSequence(sequence=[[u'a'], [u'c'], [u'c']], freq=3),
 FreqSequence(sequence=[[u'a'], [u'd']], freq=2),
 FreqSequence(sequence=[[u'a'], [u'd'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'a'], [u'f']], freq=2),
 FreqSequence(sequence=[[u'b']], freq=4),
 FreqSequence(sequence=[[u'b'], [u'a']], freq=2),
 FreqSequence(sequence=[[u'b'], [u'c']], freq=3),
 FreqSequence(sequence=[[u'b'], [u'd']], freq=2),
 FreqSequence(sequence=[[u'b'], [u'd'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'b'], [u'f']], freq=2),
 FreqSequence(sequence=[[u'b', u'a']], freq=2),
 FreqSequence(sequence=[[u'b', u'a'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'b', u'a'], [u'd']], freq=2),
 FreqSequence(sequence=[[u'b', u'a'], [u'd'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'b', u'a'], [u'f']], freq=2),
 FreqSequence(sequence=[[u'b', u'c']], freq=2),
 FreqSequence(sequence=[[u'b', u'c'], [u'a']], freq=2),
 FreqSequence(sequence=[[u'c']], freq=4),
 FreqSequence(sequence=[[u'c'], [u'a']], freq=2),
 FreqSequence(sequence=[[u'c'], [u'b']], freq=3),
 FreqSequence(sequence=[[u'c'], [u'c']], freq=3),
 FreqSequence(sequence=[[u'd']], freq=3),
 FreqSequence(sequence=[[u'd'], [u'b']], freq=2),
 FreqSequence(sequence=[[u'd'], [u'c']], freq=3),
 FreqSequence(sequence=[[u'd'], [u'c'], [u'b']], freq=2),
 FreqSequence(sequence=[[u'e']], freq=3),
 FreqSequence(sequence=[[u'e'], [u'a']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'a'], [u'b']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'a'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'a'], [u'c'], [u'b']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'b']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'b'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'c'], [u'b']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'f']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'f'], [u'b']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'f'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'e'], [u'f'], [u'c'], [u'b']], freq=2),
 FreqSequence(sequence=[[u'f']], freq=3),
 FreqSequence(sequence=[[u'f'], [u'b']], freq=2),
 FreqSequence(sequence=[[u'f'], [u'b'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'f'], [u'c']], freq=2),
 FreqSequence(sequence=[[u'f'], [u'c'], [u'b']], freq=2)]

  在訓練出模型後,我們也可以呼叫save方法將模型存到磁碟,然後在需要的時候通過FPGrowthModel或PrefixSpanModel將模型讀出來。

  以上就是用Spark學習FP Tree演算法和PrefixSpan演算法的所有內容,希望可以幫到大家。

 

(歡迎轉載,轉載請註明出處。歡迎溝通交流: liujianping-ok@163.com)

相關文章