【spark】41.Spark Mlib:FPGrowth算法

簡介

FP-Growth算法是韓嘉煒等人在2000年提出的關聯分析算法,它採起以下分治策略:將提供頻繁項集的數據庫壓縮到一棵頻繁模式樹(FP-tree),但仍保留項集關聯信息。java

在算法中使用了一種稱爲頻繁模式樹(Frequent Pattern Tree)的數據結構。FP-tree是一種特殊的前綴樹,由頻繁項頭表和項前綴樹構成。FP-Growth算法基於以上的結構加快整個挖掘過程。算法

衆所周知,Apriori算法在產生頻繁模式徹底集前須要對數據庫進行屢次掃描,同時產生大量的候選頻繁集,這就使Apriori算法時間和空間複雜度較大。可是Apriori算法中有一個很重要的性質:頻繁項集的全部非空子集都必須也是頻繁的。可是Apriori算法在挖掘額長頻繁模式的時候性能每每低下,Jiawei Han提出了FP-Growth算法。數據庫

FP-Tree:將事務數據表中的各個事務數據項按照支持度排序後,把每一個事務中的數據項按降序依次插入到一棵以 NULL爲根結點的樹中,同時在每一個結點處記錄該結點出現的支持度。apache

一、FPGrowth使用場景

FPGrowth關聯規則算法主要用於發現頻繁項集。如:沃爾瑪啤酒加尿布。api

二、FPGrowth基本概念

FPGrowth算法經過構造一個FPTree樹結構來壓縮數據記錄,使得挖掘頻繁項集只須要掃描兩次數據記錄,並且該算法不須要生成候選集合,因此效率會比較高。數據結構

那麼,如何從購物籃裏面發現尿布+啤酒這樣的最佳組合呢?ide

咱們以如下數據集爲例,假設有以下的一張購物清單表,每條記錄表明一次購物記錄:性能

|TID|Items|
|-|-|
| T1 | { 麪包 , 牛奶 } |
| T2 | { 麪包 , 尿布 , 啤酒 , 雞蛋 }|
| T3 | { 牛奶 , 尿布 , 啤酒 , 可樂 }|
| T4 | { 麪包 , 牛奶 , 尿布 , 啤酒 }|
| T5 | { 麪包 , 牛奶 , 尿布 , 可樂 }|

其中:spa

  • 牛奶、麪包叫作
  • { 牛奶、麪包}叫作項集
  • 項集出現的次數叫作支持度
  • T* 表示用戶每次的購物清單。

三、算法思想

該算法的核心就是生成一棵FPTree。前面提到過,FPTree是一種樹結構。3d

構建的過程須要將表中的數據以及關係進行保存,咱們先來看構建過程:

假設咱們的最小支持度是3,這至關因而一個閾值。接下來咱們開始按以下的步驟處理數據。

3.一、step1

掃描數據記錄,生成一級頻繁項集,並按出現次數由多到少排序,以下所示:

Item	Count
牛奶	4
麪包	4
尿布	4
啤酒	3
可樂	2(<3,刪除)
雞蛋	1(<3,刪除)

能夠看到,雞蛋和可樂在上表中要刪除,由於可樂只出現2次,雞蛋只出現1次,小於最小支持度,所以不是頻繁項集,非頻繁項集的超集必定不是頻繁項集,因此可樂和雞蛋不須要再考慮。

3.二、step2

再次掃描數據記錄,對每條記錄中出如今Step 1產生的表中的項,按表中的順序排序。初始時,新建一個根結點,標記爲null。而後依次掃描每條記錄,構建FPTree。

一、第一條記錄:{麪包,牛奶}須要根據Step1中結果轉換成:{牛奶,麪包},新建一個結點,name爲{牛奶},將其插入到根節點下,並設置count爲1,而後新建一個{麪包}結點,插入到{牛奶}結點下面,插入後以下所示:

二、第二條記錄:{麪包,尿布,啤酒,雞蛋},過濾並排序後爲:{麪包,尿布,啤酒},發現根結點沒有包含{麪包}的兒子(有一個{麪包}孫子但不是兒子),所以新建一個{麪包}結點,插在根結點下面,這樣根結點就有了兩個孩子,隨後新建{尿布}結點插在{麪包}結點下面,新建{啤酒}結點插在{尿布}下面,插入後以下所示:

三、第三條記錄:{牛奶,尿布,啤酒,可樂},過濾並排序後爲:{牛奶,尿布,啤酒},這時候發現根結點有兒子{牛奶},所以不須要新建結點,只需將原來的{牛奶}結點的count加1便可,往下發現{牛奶}結點有一個兒子{尿布},因而新建{尿布}結點,並插入到{牛奶}結點下面,隨後新建{啤酒}結點插入到{尿布}結點後面。插入後以下圖所示:

四、第四條記錄:{麪包,牛奶,尿布,啤酒},過濾並排序後爲:{牛奶,麪包,尿布,啤酒},這時候發現根結點有兒子{牛奶},所以不須要新建結點,只需將原來的{牛奶}結點的count加1便可,往下發現{牛奶}結點有一個兒子{麪包},因而也不須要新建{麪包}結點,只需將原來{麪包}結點的count加1,因爲這個{麪包}結點沒有兒子,此時需新建{尿布}結點,插在{麪包}結點下面,隨後新建{啤酒}結點,插在{尿布}結點下面,插入後以下圖所示:

五、按照1-4這樣的方式迭代全部的記錄,最終會生成一棵樹,即FPTree。按上表中生成的最終的FPTree以下圖所示:

樹中每一個路徑表明一個項集,由於許多項集有公共項,並且出現次數越多的項越多是公共項,所以按出現次數由多到少的順序排序能夠節省空間,實現壓縮存儲。

另外咱們須要一個表頭和對每個name相同的結點作一個線索,方便後面使用,線索的構造也是在建樹過程造成的(下圖虛線)。

至此,整個FpTree就構造好了。

四、利用FPTree挖掘頻繁項集

FPTree建好後,就能夠進行頻繁項集的挖掘,挖掘算法稱爲FPGrowth(Frequent Pattern Growth)算法,挖掘從表頭header的最後一個項開始。

此處即從{啤酒}開始,根據{啤酒}的線索鏈找到全部{啤酒}結點,而後找出每一個{啤酒}結點的分支:{牛奶,麪包,尿布,啤酒:1},{牛奶,尿布,啤酒:1},{麪包,尿布,啤酒:1},其中的「1」表示出現1次。

注意,雖然{牛奶}出現4次,但{牛奶,麪包,尿布,啤酒}只同時出現1次,所以分支的count是由後綴結點{啤酒}的count決定的,除去{啤酒},咱們獲得對應的前綴路徑{牛奶,麪包,尿布:1},{牛奶,尿布:1},{麪包,尿布:1},根據前綴路徑咱們能夠生成一棵條件FPTree,構造方式跟以前同樣,此處的數據記錄變爲:

T1	{牛奶,麪包,尿布 : 1}
T2	{牛奶,尿布: 1}
T3	{麪包,尿布: 1}

絕對支持度依然是3,咱們發現此時,牛奶的支持度爲二、麪包的支持度爲二、尿布的支持度爲3,因爲咱們的支持度爲3,因此刪除牛奶和麪包。按照相同的算法構造獲得的FPTree爲:

構造好條件樹後,對條件樹進行遞歸挖掘,當條件樹只有一條路徑時,路徑的全部組合即爲條件頻繁集,假設{啤酒}的條件頻繁集爲{S1,S2},則{啤酒}的頻繁集爲{S1+{啤酒},S2+{啤酒},S1+S2+{啤酒}},即{啤酒}的頻繁集必定有相同的後綴{啤酒},此處的條件頻繁集爲:{{},{尿布}},因而{啤酒}的頻繁集爲{{啤酒}{尿布,啤酒}}。

接下來找header表頭的倒數第二個項{尿布}的頻繁集,同上能夠獲得{尿布}的前綴路徑爲:{麪包:1},{牛奶:1},{牛奶,麪包:2},條件FPTree的數據集爲:

T1	{麪包 :1 }
T2	{牛奶 :1}
T3	{牛奶,麪包 :2}

構造的條件FpTree爲:

這顆條件樹路徑上的全部組合即爲條件頻繁集:

{{},{牛奶},{麪包},{牛奶,麪包}}

加上{尿布}後,又獲得一組頻繁項集

{{尿布},{牛奶,尿布},{麪包,尿布},{牛奶,麪包,尿布}}

一樣,這組頻繁項集必定包含一個相同的後綴:{尿布},也就是咱們一開始分析的對象,而且不包含{啤酒},所以這一組頻繁項集與上一組不會重複。

重複以上步驟,對header表頭的每一個項進行挖掘,便可獲得整個頻繁項集,頻繁項集即不重複也不遺漏。

最終,咱們能夠獲得多個集合列表,每個集合表明一個頻繁項集。

總結下來,FPTree的開發流程爲2個:

一、生成樹過程

二、挖掘樹過程:挖掘樹的過程也是一個生成樹的過程,每次挖掘一個節點的目的,都是爲了發現該節點的頻繁項集,將最終生成的結果取全部子集而後將每一項與挖掘的節點組合,做爲咱們最後獲得的結果。

Spark Mlib實現

訓練集以下:

麪包 牛奶
麪包 尿布 啤酒 雞蛋
牛奶 尿布 啤酒 可樂
麪包 牛奶 尿布 啤酒
麪包 牛奶 尿布 可樂

存儲在路徑data/sample_fpgrowth.txt中。

接下來調用FPGrouwth算法訓練數據集。

package com.zhaoyi;// $example on$
import java.util.Arrays;
import java.util.List;

import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.mllib.fpm.AssociationRules;
import org.apache.spark.mllib.fpm.FPGrowth;
import org.apache.spark.mllib.fpm.FPGrowthModel;
// $example off$

import org.apache.spark.SparkConf;

public class JavaSimpleFPGrowth {

  public static void main(String[] args) {
    SparkConf conf = new SparkConf().setMaster("local[*]").set("spark.testing.memory","2140000000")
            .setAppName("JavaLinearRegressionWithSGDExample");
    JavaSparkContext sc = new JavaSparkContext(conf);

    // $example on$
    JavaRDD<String> data = sc.textFile("data/sample_fpgrowth.txt");

    JavaRDD<List<String>> transactions = data.map(line -> Arrays.asList(line.split(" ")));

    // 最小支持度爲0.5
    FPGrowth fpg = new FPGrowth()
      .setMinSupport(0.5)
      .setNumPartitions(10);
    FPGrowthModel<String> model = fpg.run(transactions);

    for (FPGrowth.FreqItemset<String> itemset: model.freqItemsets().toJavaRDD().collect()) {
      System.out.println("[" + itemset.javaItems() + "], " + itemset.freq());
    }

    double minConfidence = 0.8;
    for (AssociationRules.Rule<String> rule
      : model.generateAssociationRules(minConfidence).toJavaRDD().collect()) {
      System.out.println(
        rule.javaAntecedent() + " => " + rule.javaConsequent() + ", " + rule.confidence());
    }
    // $example off$

    sc.stop();
  }
}
麪包 牛奶
麪包 尿布 啤酒 雞蛋
牛奶 尿布 啤酒 可樂
麪包 牛奶 尿布 啤酒
麪包 牛奶 尿布 可樂

能夠看到,咱們設置的最小支持度爲0.5,也就是說,過濾過程當中,會將低於出現次數小於3/5>0.5>2/5次的話,顯然可樂(出現一次,支持度爲2/5=0.4)以及雞蛋(出現1次,支持度爲1/5=0.2)都不會歸入頻繁項集之中,在step1中就會被T除。

最終的輸出結果

[[尿布]], 4
[[牛奶]], 4
[[牛奶, 尿布]], 3
[[麪包]], 4
[[麪包, 牛奶]], 3
[[麪包, 尿布]], 3
[[啤酒]], 3
[[啤酒, 尿布]], 3

[啤酒] => [尿布], 1.0

能夠看到,該輸出結果完美的實現了牛奶尿布實例中的預測結果。即{啤酒,尿布}這樣的組合頻繁項集。

那麼,咱們能夠考慮將這兩種商品擺放到一塊兒,從而起到必定的增長銷售業績的做用(事實上沃爾瑪的確這麼作了)。

相關文章
相關標籤/搜索