1. 程式人生 > >一步步教你輕鬆學關聯規則Apriori演算法

一步步教你輕鬆學關聯規則Apriori演算法

摘要:先驗演算法(Apriori Algorithm)是關聯規則學習的經典演算法之一,常常應用在商業等諸多領域。本文首先介紹什麼是Apriori演算法,與其相關的基本術語,之後對演算法原理進行多方面剖析,其中包括思路、原理、優缺點、流程步驟和應用場景。接著再通過一個實際案例進行語言描述性逐步剖析。至此,讀者基本瞭解該演算法思想和過程。緊接著我們進行實驗,重點的頻繁項集的生成和關聯規則的生成。最後我們採用綜合例項進行實際演示。(本文原創,轉載必須註明出處.)

目錄

理論介紹

演算法概述

維基百科

在電腦科學以及資料探勘領域中,先驗演算法(Apriori Algorithm)是關聯規則學習的經典演算法之一。先驗演算法的設計目的是為了處理包含交易資訊內容的資料庫(例如,顧客購買的商品清單,或者網頁常訪清單。)而其他的演算法則是設計用來尋找無交易資訊(如Winepi演算法和Minepi演算法)或無時間標記(如DNA測序)的資料之間的聯絡規則。

先驗演算法採用廣度優先搜尋演算法進行搜尋並採用樹結構來對候選專案集進行高效計數。它通過長度為\( k-1 \)的候選專案集來產生長度為 k 的候選專案集,然後從中刪除包含不常見子模式的候選項。根據向下封閉性引理,該候選專案集包含所有長度為 k 的頻繁專案集。之後,就可以通過掃描交易資料庫來決定候選專案集中的頻繁專案集。

資料探勘十大演算法

Apriori 演算法是一種最有影響力的挖掘布林關聯規則的頻繁項集演算法,它是由Rakesh Agrawal 和RamakrishnanSkrikant 提出的。它使用一種稱作逐層搜尋的迭代方法,k- 項集用於探索(k+1)- 項集。首先,找出頻繁 1- 項集的集合。該集合記作L1。L1 用於找頻繁2- 項集的集合 L2,而L2 用於找L3,如此下去,直到不能找到 k- 項集。每找一個 Lk 需要一次資料庫掃描。為提高頻繁項集逐層產生的效率,一種稱作Apriori 性質用於壓縮搜尋空間。其約束條件:一是頻繁項集的所有非空子集都必須也是頻繁的,二是非頻繁項集的所有父集都是非頻繁的。

基本概念

關聯分析

關聯分析是一種在大規模資料集中尋找相互關係的任務。 這些關係可以有兩種形式:

  • 頻繁項集(frequent item sets): 經常出現在一塊的物品的集合。
  • 關聯規則(associational rules): 暗示兩種物品之間可能存在很強的關係。

相關術語

  • 關聯分析(關聯規則學習): 下面是用一個 雜貨店簡單交易清單的例子來說明這兩個概念,如下表所示:


  • 頻繁項集: {葡萄酒, 尿布, 豆奶} 就是一個頻繁項集的例子。
  • 關聯規則: 尿布 -> 葡萄酒 就是一個關聯規則。這意味著如果顧客買了尿布,那麼他很可能會買葡萄酒。
  • 支援度: 資料集中包含該項集的記錄所佔的比例。例如上圖中,{豆奶} 的支援度為 4/5。{豆奶, 尿布} 的支援度為 3/5。
  • 可信度: 針對一條諸如 {尿布} -> {葡萄酒} 這樣具體的關聯規則來定義的。這條規則的 可信度 被定義為 支援度({尿布, 葡萄酒})/支援度({尿布}),支援度({尿布, 葡萄酒}) = 3/5,支援度({尿布}) = 4/5,所以 {尿布} -> {葡萄酒} 的可信度 = 3/5 / 4/5 = 3/4 = 0.75。

    支援度 和 可信度 是用來量化 關聯分析 是否成功的一個方法。 假設想找到支援度大於 0.8 的所有項集,應該如何去做呢? 一個辦法是生成一個物品所有可能組合的清單,然後對每一種組合統計它出現的頻繁程度,但是當物品成千上萬時,上述做法就非常非常慢了。 我們需要詳細分析下這種情況並討論下 Apriori 原理,該原理會減少關聯規則學習時所需的計算量。

  • k項集
    如果事件A中包含k個元素,那麼稱這個事件A為k項集,並且事件A滿足最小支援度閾值的事件稱為頻繁k項集。

  • 由頻繁項集產生強關聯規則

    • K維資料項集LK是頻繁項集的必要條件是它所有K-1維子項集也為頻繁項集,記為LK-1 
    • 如果K維資料項集LK的任意一個K-1維子集Lk-1,不是頻繁項集,則K維資料項集LK本身也不是最大資料項集。
    • Lk是K維頻繁項集,如果所有K-1維頻繁項集合Lk-1中包含LK的K-1維子項集的個數小於K,則Lk不可能是K維最大頻繁資料項集。
    • 同時滿足最小支援度閥值和最小置信度閥值的規則稱為強規則。

演算法原理

Apriori 思想

演算法思想

首先找出所有的頻集,這些項集出現的頻繁性至少和預定義的最小支援度一樣。然後由頻集產生強關聯規則,這些規則必須滿足最小支援度和最小可信度。然後使用第1步找到的頻集產生期望的規則,產生只包含集合的項的所有規則,其中每一條規則的右部只有一項,這裡採用的是中規則的定義。一旦這些規則被生成,那麼只有那些大於使用者給定的最小可信度的規則才被留下來。

Apriori演算法過程

第一步通過迭代,檢索出事務資料庫中的所有頻繁項集,即支援度不低於使用者設定的閾值的項集;
第二步利用頻繁項集構造出滿足使用者最小信任度的規則。

具體做法就是:
首先找出頻繁1-項集,記為L1;然後利用L1來產生候選項集C2,對C2中的項進行判定挖掘出L2,即頻繁2-項集;不斷如此迴圈下去直到無法發現更多的頻繁k-項集為止。每挖掘一層Lk就需要掃描整個資料庫一遍。演算法利用了一個性質:任一頻繁項集的所有非空子集也必須是頻繁的。

Apriori 原理

假設我們一共有 4 個商品: 商品0, 商品1, 商品2, 商品3。 所有可能的情況如下:

如果我們計算所有組合的支援度,也需要計算 15 次。即 \( 2^N - 1 = 2^4 - 1 = 15 \)。隨著物品的增加,計算的次數呈指數的形式增長 。為了降低計算次數和時間,研究人員發現了一種所謂的 Apriori 原理,即某個項集是頻繁的,那麼它的所有子集也是頻繁的。 例如,如果 {0, 1} 是頻繁的,那麼 {0}, {1} 也是頻繁的。 該原理直觀上沒有什麼幫助,但是如果反過來看就有用了,也就是說如果一個項集是 非頻繁項集,那麼它的所有超集也是非頻繁項集,如下圖所示:

在圖中我們可以看到,已知灰色部分 {2,3} 是 非頻繁項集,那麼利用上面的知識,我們就可以知道 {0,2,3} {1,2,3} {0,1,2,3} 都是 非頻繁的。 也就是說,計算出 {2,3} 的支援度,知道它是 非頻繁 的之後,就不需要再計算 {0,2,3} {1,2,3} {0,1,2,3} 的支援度,因為我們知道這些集合不會滿足我們的要求。 使用該原理就可以避免項集數目的指數增長,從而在合理的時間內計算出頻繁項集。

Apriori 演算法優缺點

  • 優點:易編碼實現
  • 缺點:在大資料集上可能較慢
  • 適用資料型別:數值型 或者 標稱型資料。

Apriori 演算法流程步驟:

  • 收集資料:使用任意方法。
  • 準備資料:任何資料型別都可以,因為我們只儲存集合。
  • 分析資料:使用任意方法。
  • 訓練資料:使用Apiori演算法來找到頻繁項集。
  • 測試演算法:不需要測試過程。
  • 使用演算法:用於發現頻繁項集以及物品之間的關聯規則。

應用場景

Apriori 演算法廣泛應用於各種領域,通過對資料的關聯性進行了分析和挖掘,挖掘出的這些資訊在決策制定過程中具有重要的參考價值。

  • Apriori演算法廣泛應用於消費市場價格分析中

    它能夠很快的求出各種產品之間的價格關係和它們之間的影響。通過資料探勘,市場商人可以瞄準目標客戶,採用個人股票行市、最新資訊、特殊的市場推廣活動或其他一些特殊的資訊手段,從而極大地減少廣告預算和增加收入。百貨商場、超市和一些老字型大小的零售店也在進行資料探勘,以便猜測這些年來顧客的消費習慣。

  • Apriori演算法應用於網路安全領域,比如網路入侵檢測技術中。

    早期中大型的電腦系統中都收集審計資訊來建立跟蹤檔,這些審計跟蹤的目的多是為了效能測試或計費,因此對攻擊檢測提供的有用資訊比較少。它通過模式的學習和訓練可以發現網路使用者的異常行為模式。採用作用度的Apriori演算法削弱了Apriori演算法的挖掘結果規則,是網路入侵檢測系統可以快速的發現使用者的行為模式,能夠快速的鎖定攻擊者,提高了基於關聯規則的入侵檢測系統的檢測性。

  • Apriori演算法應用於高校管理中。
    隨著高校貧困生人數的不斷增加,學校管理部門資助工作難度也越加增大。針對這一現象,提出一種基於資料探勘演算法的解決方法。將關聯規則的Apriori演算法應用到貧困助學體系中,並且針對經典Apriori挖掘演算法存在的不足進行改進,先將事務資料庫對映為一個布林矩陣,用一種逐層遞增的思想來動態的分配記憶體進行儲存,再利用向量求"與"運算,尋找頻繁項集。實驗結果表明,改進後的Apriori演算法在執行效率上有了很大的提升,挖掘出的規則也可以有效地輔助學校管理部門有針對性的開展貧困助學工作。

  • Apriori演算法被廣泛應用於行動通訊領域。

    移動增值業務逐漸成為行動通訊市場上最有活力、最具潛力、最受矚目的業務。隨著產業的復甦,越來越多的增值業務表現出強勁的發展勢頭,呈現出應用多元化、營銷品牌化、管理集中化、合作縱深化的特點。針對這種趨勢,在關聯規則資料探勘中廣泛應用的Apriori演算法被很多公司應用。依託某電信運營商正在建設的增值業務Web資料倉庫平臺,對來自移動增值業務方面的調查資料進行了相關的挖掘處理,從而獲得了關於使用者行為特徵和需求的間接反映市場動態的有用資訊,這些資訊在指導運營商的業務運營和輔助業務提供商的決策制定等方面具有十分重要的參考價值。

Apriori 例項理解

例項理解1

一個大型超級市場根據最小存貨單位(SKU)來追蹤每件物品的銷售資料。從而也可以得知哪裡物品通常被同時購買。通過採用先驗演算法來從這些銷售資料中建立頻繁購買商品組合的清單是一個效率適中的方法。假設交易資料庫包含以下子集{1,2,3,4},{1,2},{2,3,4},{2,3},{1,2,4},{3,4},{2,4}。每個標號表示一種商品,如“黃油”或“麵包”。先驗演算法首先要分別計算單個商品的購買頻率。下表解釋了先驗演算法得出的單個商品購買頻率。

然後我們可以定義一個最少購買次數來定義所謂的“頻繁”。在這個例子中,我們定義最少的購買次數為3。因此,所有的購買都為頻繁購買。接下來,就要生成頻繁購買商品的組合及購買頻率。先驗演算法通過修改樹結構中的所有可能子集來進行這一步驟。然後我們僅重新選擇頻繁購買的商品組合:

並且生成一個包含3件商品的頻繁組合列表(通過將頻繁購買商品組合與頻繁購買的單件商品聯絡起來得出)。在上述例子中,不存在包含3件商品組合的頻繁組合。最常見的3件商品組合為{1,2,4}和{2,3,4},但是他們的購買次數為2,低於我們設定的最低購買次數。

例項理解2

假設有一個數據庫D,其中有4個事務記錄,分別表示為:

這裡預定最小支援度minSupport=2,下面用圖例說明演算法執行的過程:
1、掃描D,對每個候選項進行支援度計數得到表C1:

2、比較候選項支援度計數與最小支援度minSupport(假設為2),產生1維最大專案集L1:

3、由L1產生候選項集C2:

 

4、掃描D,對每個候選項集進行支援度計數:

5、比較候選項支援度計數與最小支援度minSupport,產生2維最大專案集L2:

6、由L2產生候選項集C3:

7、比較候選項支援度計數與最小支援度minSupport,產生3維最大專案集L3:

演算法終止。

從整體同樣的能說明此過程

首先我們收集所有資料集(可以理解為商品清單),經過資料預處理後如Database TDB所示。我們掃描資料集,經過第一步對每個候選項進行支援度計數得到表C1,比較候選項支援度計數與最小支援度minSupport(假設最小支援度為2),產生1維最大專案集L1。再對L1進行組合產生候選項集C2。第二步我們對C2進行支援度計數,比較候選項支援度計數與最小支援度minSupport,產生2維最大專案集L2。由L2產生候選項集C3,對C3進行支援度計數,使用Apriori性質剪枝:頻繁項集的所有子集必須是頻繁的,對候選項C3,我們可以刪除其子集為非頻繁的選項,{A,B,C}的2項子集是{A,B},{A,C},{B,C},其中{A,B}不是L2的元素,所以刪除這個選項;{A,C,E}的2項子集是{A,C},{A,E},{C,E},其中{A,E} 不是L2的元素,所以刪除這個選項;{B,C,E}的2項子集是{B,C},{B,E},{C,E},它的所有2-項子集都是L2的元素,因此保留這個選項。這樣,剪枝後得到{B,C,E},比較候選項支援度計數與最小支援度minSupport,產生3維最大專案集L3:繼續進行沒有滿足條件,演算法終止。

Apriori 演算法實現

關聯分析的目標包括兩項:發現頻繁項集和發現關聯規則。Apriori演算法是發現頻繁項集的一種方法。 Apriori 演算法的兩個輸入引數分別是最小支援度和資料集。該演算法首先會生成所有單個物品的項集列表。接著掃描交易記錄來檢視哪些項集滿足最小支援度要求,那些不滿足最小支援度要求的集合會被去掉。然後對剩下來的集合進行組合以生成包含兩個元素的項集。接下來再重新掃描交易記錄,去掉不滿足最小支援度的項集。該過程重複進行直到所有項集被去掉。

生成候選項集

下面會建立一個用於構建初始集合的函式,也會建立一個通過掃描資料集以尋找交易記錄子集的函式, 資料掃描的虛擬碼如下:

- 對資料集中的每條交易記錄 tran
- 對每個候選項集 can
  - 檢查一下 can 是否是 tran 的子集: 如果是則增加 can 的計數值
- 對每個候選項集
  - 如果其支援度不低於最小值,則保留該項集
  - 返回所有頻繁項集列表 以下是一些輔助函式。

第一步載入資料集,

# 載入資料集
def loadDataSet():
    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

執行結果如下:

[[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

第二步建立集合 C1。

對 dataSet 進行去重,排序,放入 list 中,然後轉換所有的元素為 frozenset

'''建立集合C1即對dataSet去重排序'''
def createC1(dataSet):
    C1 = []
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])
    C1.sort()
    # frozenset表示凍結的set 集合,元素無改變把它當字典的 key 來使用
    return C1
    # return map(frozenset, C1)

執行結果如下,注意map(frozenset, C1)在後面會用到,注意便於計數處理:

[[1], [2], [3], [4], [5]]

第三步計算候選資料集CK在資料集D中的支援度。

''' 計算候選資料集CK在資料集D中的支援度,返回大於最小支援度的資料'''
def scanD(D,Ck,minSupport):
    # ssCnt 臨時存放所有候選項集和頻率.
    ssCnt = {}
    for tid in D:
        # print('1:',tid)
        for can in map(frozenset,Ck):      #每個候選項集can
            # print('2:',can.issubset(tid),can,tid)
            if can.issubset(tid):
                if not can in ssCnt:
                    ssCnt[can] = 1
                else:
                    ssCnt[can] +=1

    numItems = float(len(D)) # 所有項集數目
    # 滿足最小支援度的頻繁項集
    retList  = []
    # 滿足最小支援度的頻繁項集和頻率
    supportData = {}

    for key in ssCnt:
        support = ssCnt[key]/numItems   #除以總的記錄條數,即為其支援度
        if support >= minSupport:
            retList.insert(0,key)       #超過最小支援度的項集,將其記錄下來。
        supportData[key] = support
    return retList, supportData

執行結果如下:

滿足最小支援度的頻繁項集是:
[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})] 
頻繁項集的支援度
{frozenset({4}): 0.25, frozenset({5}): 0.75, frozenset({2}): 0.75, frozenset({3}): 0.75, frozenset({1}): 0.5}

第四步 根據上步Lk計算可能的候選項集 Ck

''' Apriori演算法:輸入頻繁項集列表Lk,輸出所有可能的候選項集 Ck'''
def aprioriGen(Lk, k):
    retList = [] # 滿足條件的頻繁項集
    lenLk = len(Lk)
    for i in range(lenLk):
        for j in range(i+1, lenLk):
            L1 = list(Lk[i])[: k-2]
            L2 = list(Lk[j])[: k-2]
            # print '-----i=', i, k-2, Lk, Lk[i], list(Lk[i])[: k-2]
            # print '-----j=', j, k-2, Lk, Lk[j], list(Lk[j])[: k-2]
            L1.sort()
            L2.sort()
            if L1 == L2:
                retList.append(Lk[i] | Lk[j])
    return retList

L1,k=2的執行結果:
[frozenset({1, 3}), frozenset({1, 2}), frozenset({1, 5}), frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5})]

第五步: 找出滿足最小支援度的頻繁項集。

'''找出資料集中支援度不小於最小支援度的候選項集以及它們的支援度即頻繁項集。
演算法思想:首先構建集合C1,然後掃描資料集來判斷這些只有一個元素的項集是否滿足最小支援度。滿足最小支援度要求的項集構成集合L1。然後L1 中的元素相互組合成C2,C2再進一步過濾變成L2,以此類推,直到C_n的長度為0時結束,即可找出所有頻繁項集的支援度。
返回:L 頻繁項集的全集
      supportData 所有元素和支援度的全集
'''
def apriori(dataSet, minSupport=0.5):
    # C1即對dataSet去重排序,然後轉換所有的元素為frozenset
    C1 = createC1(dataSet)
    # 對每一行進行 set 轉換,然後存放到集合中
    D = list(map(set, dataSet))
    # 計算候選資料集C1在資料集D中的支援度,並返回支援度大於minSupport 的資料
    L1, supportData = scanD(D, C1, minSupport)
    # L 加了一層 list, L一共 2 層 list
    L = [L1];k = 2
    # 判斷L第k-2項的資料長度是否>0即頻繁項集第一項。第一次執行時 L 為 [[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])]]。L[k-2]=L[0]=[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])],最後面 k += 1
    while (len(L[k-2]) > 0):
        Ck = aprioriGen(L[k-2], k) # 例如: 以 {0},{1},{2} 為輸入且 k = 2 則輸出 {0,1}, {0,2}, {1,2}. 以 {0,1},{0,2},{1,2} 為輸入且 k = 3 則輸出 {0,1,2}

        # 返回候選資料集CK在資料集D中的支援度大於最小支援度的資料
        Lk, supK = scanD(D, Ck, minSupport)
        # 儲存所有候選項集的支援度,如果字典沒有就追加元素,如果有就更新元素
        supportData.update(supK)
        if len(Lk) == 0:
            break
        # Lk 表示滿足頻繁子項的集合,L 元素在增加,例如:
        # l=[[set(1), set(2), set(3)]]
        # l=[[set(1), set(2), set(3)], [set(1, 2), set(2, 3)]]
        L.append(Lk)
        k += 1
    return L, supportData

我們寫個測試以上程式碼

'''測試頻繁項集生產'''
def testApriori():
    # 載入測試資料集
    dataSet = loadDataSet()
    print ('dataSet: ', dataSet)

    # Apriori 演算法生成頻繁項集以及它們的支援度
    L1, supportData1 = apriori(dataSet, minSupport=0.7)
    print ('L(0.7): ', L1)
    print ('supportData(0.7): ', supportData1)

    print ('->->->->->->->->->->->->->->->->->->->->->->->->->->->->')

    # Apriori 演算法生成頻繁項集以及它們的支援度
    L2, supportData2 = apriori(dataSet, minSupport=0.5)
    print ('L(0.5): ', L2)
    print ('supportData(0.5): ', supportData2)

執行結果如下:

dataSet:  [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
L(0.7):  [[frozenset({3}), frozenset({2}), frozenset({5})], [frozenset({2, 5})]]
supportData(0.7):  {frozenset({5}): 0.75, frozenset({3}): 0.75, frozenset({3, 5}): 0.5, frozenset({4}): 0.25, frozenset({2, 3}): 0.5, frozenset({2, 5}): 0.75, frozenset({1}): 0.5, frozenset({2}): 0.75}
->->->->->->->->->->->->->->->->->->->->->->->->->->->->
L(0.5):  [[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})], [frozenset({3, 5}), frozenset({1, 3}), frozenset({2, 5}), frozenset({2, 3})], [frozenset({2, 3, 5})]]
supportData(0.5):  {frozenset({5}): 0.75, frozenset({3}): 0.75, frozenset({2, 3, 5}): 0.5, frozenset({1, 2}): 0.25, frozenset({1, 5}): 0.25, frozenset({3, 5}): 0.5, frozenset({4}): 0.25, frozenset({2, 3}): 0.5, frozenset({2, 5}): 0.75, frozenset({1}): 0.5, frozenset({1, 3}): 0.5, frozenset({2}): 0.75}

到這一步,我們就找出我們所需要的 頻繁項集 和他們的 支援度 了,接下來再找出關聯規則即可!

第六步從頻繁項集中挖掘關聯規則

集合中的元素是不重複的,但我們想知道基於這些元素能否獲得其它內容。 某個元素或某個元素集合可能會推匯出另一個元素。 從先前 雜貨店 的例子可以得到,如果有一個頻繁項集 {豆奶,萵苣},那麼就可能有一條關聯規則 “豆奶 -> 萵苣”。 這意味著如果有人買了豆奶,那麼在統計上他會購買萵苣的概率比較大。 但是,這一條件反過來並不總是成立。 也就是說 “豆奶 -> 萵苣” 統計上顯著,那麼 “萵苣 -> 豆奶” 也不一定成立。

前面我們給出了 頻繁項集 的量化定義,即它滿足最小支援度要求。對於 關聯規則,我們也有類似的量化方法,這種量化指標稱之為 可信度。
一條規則 A -> B 的可信度定義為 support(A | B) / support(A)。(注意: 在 python 中 | 表示集合的並操作,而數學書集合並的符號是 U)。A | B 是指所有出現在集合 A 或者集合 B 中的元素。由於我們先前已經計算出所有 頻繁項集 的支援度了,現在我們要做的只不過是提取這些資料做一次除法運算即可。

一個頻繁項集可以產生多少條關聯規則呢?

如下圖所示,給出的是項集 {0,1,2,3} 產生的所有關聯規則:

與我們前面的 頻繁項集 生成一樣,我們可以為每個頻繁項集產生許多關聯規則。如果能減少規則的數目來確保問題的可解析,那麼計算起來就會好很多。通過觀察,我們可以知道,如果某條規則並不滿足 最小可信度 要求,那麼該規則的所有子集也不會滿足 最小可信度 的要求。
如上圖所示,假設 123 -> 3 並不滿足最小可信度要求,那麼就知道任何左部為 {0,1,2} 子集的規則也不會滿足 最小可信度 的要求。 即 12 -> 03 , 02 -> 13 , 01 -> 23 , 2 -> 013, 1 -> 023, 0 -> 123 都不滿足 最小可信度 要求。可以利用關聯規則的上述性質屬性來減少需要測試的規則數目,跟先前 Apriori 演算法的套路一樣。
以下是一些輔助函式:

計算可信度

'''計算可信度(confidence)
Args:
    freqSet 頻繁項集中的元素,例如: frozenset([1, 3])
    H 頻繁項集中的元素的集合,例如: [frozenset([1]), frozenset([3])]
    supportData 所有元素的支援度的字典
    brl 關聯規則列表的空陣列
    minConf 最小可信度
Returns:
    prunedH 記錄 可信度大於閾值的集合
'''
def calcConf(freqSet, H, supportData, brl, minConf=0.7):
    # 記錄可信度大於最小可信度(minConf)的集合
    prunedH = []
    for conseq in H: # 假設 freqSet = frozenset([1, 3]), H = [frozenset([1]), frozenset([3])],那麼現在需要求出 frozenset([1]) -> frozenset([3]) 的可信度和 frozenset([3]) -> frozenset([1]) 的可信度
        conf = supportData[freqSet]/supportData[freqSet-conseq] # 支援度定義: a -> b = support(a | b) / support(a). 假設  freqSet = frozenset([1, 3]), conseq = [frozenset([1])],那麼 frozenset([1]) 至 frozenset([3]) 的可信度為 = support(a | b) / support(a) = supportData[freqSet]/supportData[freqSet-conseq] = supportData[frozenset([1, 3])] / supportData[frozenset([1])]
        if conf >= minConf:
            # 只要買了 freqSet-conseq 集合,一定會買 conseq 集合(freqSet-conseq 集合和 conseq集合 是全集)
            print (freqSet-conseq, '-->', conseq, 'conf:', conf)
            brl.append((freqSet-conseq, conseq, conf))
            prunedH.append(conseq)
    return prunedH

遞迴計算頻繁項集的規則

"""遞迴計算頻繁項集的規則
    Args:
        freqSet 頻繁項集中的元素,例如: frozenset([2, 3, 5])
        H 頻繁項集中的元素的集合,例如: [frozenset([2]), frozenset([3]), frozenset([5])]
        supportData 所有元素的支援度的字典
        brl 關聯規則列表的陣列
        minConf 最小可信度
"""
def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
    # H[0] 是 freqSet 的元素組合的第一個元素,並且 H 中所有元素的長度都一樣,長度由 aprioriGen(H, m+1) 這裡的 m + 1 來控制
    # 該函式遞迴時,H[0] 的長度從 1 開始增長 1 2 3 ...
    # 假設 freqSet = frozenset([2, 3, 5]), H = [frozenset([2]), frozenset([3]), frozenset([5])]
    # 那麼 m = len(H[0]) 的遞迴的值依次為 1 2
    # 在 m = 2 時, 跳出該遞迴。假設再遞迴一次,那麼 H[0] = frozenset([2, 3, 5]),freqSet = frozenset([2, 3, 5]) ,沒必要再計算 freqSet 與 H[0] 的關聯規則了。
    m = len(H[0])
    if (len(freqSet) > (m + 1)):
        # 生成 m+1 個長度的所有可能的 H 中的組合,假設 H = [frozenset([2]), frozenset([3]), frozenset([5])]
        # 第一次遞迴呼叫時生成 [frozenset([2, 3]), frozenset([2, 5]), frozenset([3, 5])]
        # 第二次 。。。沒有第二次,遞迴條件判斷時已經退出了
        Hmp1 = aprioriGen(H, m+1)
        # 返回可信度大於最小可信度的集合
        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
        # print ('Hmp1=', Hmp1)
        # print ('len(Hmp1)=', len(Hmp1), 'len(freqSet)=', len(freqSet))
        # 計算可信度後,還有資料大於最小可信度的話,那麼繼續遞迴呼叫,否則跳出遞迴
        if (len(Hmp1) > 1):
            # print '----------------------', Hmp1
            # print len(freqSet),  len(Hmp1[0]) + 1
            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)

生成關聯規則

'''生成關聯規則
    Args:
        L 頻繁項集列表
        supportData 頻繁項集支援度的字典
        minConf 最小置信度
    Returns:
        bigRuleList 可信度規則列表(關於 (A->B+置信度) 3個欄位的組合)
'''
def generateRules(L, supportData, minConf=0.7):
    bigRuleList = []
    for i in range(1, len(L)):
        # 獲取頻繁項集中每個組合的所有元素
        for freqSet in L[i]:
            # 組合總的元素並遍歷子元素,轉化為 frozenset集合存放到 list 列表中
            H1 = [frozenset([item]) for item in freqSet]
            # print(H1)
            # 2 個的組合else, 2 個以上的組合 if
            if (i > 1):
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            else:
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList

到這裡為止,通過呼叫 generateRules 函式即可得出我們所需的 關聯規則。測試下結果:

def testGenerateRules():
    # 載入測試資料集
    dataSet = loadDataSet()
    print ('dataSet: ', dataSet)

    # Apriori 演算法生成頻繁項集以及它們的支援度
    L1, supportData1 = apriori(dataSet, minSupport=0.5)
    print ('L(0.7): ', L1)
    print ('supportData(0.7): ', supportData1)

    # 生成關聯規則
    rules = generateRules(L1, supportData1, minConf=0.5)
    print ('rules: ', rules)

執行結果如下:

dataSet:  [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
L(0.7):  [[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})], [frozenset({3, 5}), frozenset({1, 3}), frozenset({2, 5}), frozenset({2, 3})], [frozenset({2, 3, 5})]]
supportData(0.7):  {frozenset({5}): 0.75, frozenset({3}): 0.75, frozenset({2, 3, 5}): 0.5, frozenset({1, 2}): 0.25, frozenset({1, 5}): 0.25, frozenset({3, 5}): 0.5, frozenset({4}): 0.25, frozenset({2, 3}): 0.5, frozenset({2, 5}): 0.75, frozenset({1}): 0.5, frozenset({1, 3}): 0.5, frozenset({2}): 0.75}
frozenset({5}) --> frozenset({3}) conf: 0.6666666666666666
frozenset({3}) --> frozenset({5}) conf: 0.6666666666666666
frozenset({3}) --> frozenset({1}) conf: 0.6666666666666666
frozenset({1}) --> frozenset({3}) conf: 1.0
frozenset({5}) --> frozenset({2}) conf: 1.0
frozenset({2}) --> frozenset({5}) conf: 1.0
frozenset({3}) --> frozenset({2}) conf: 0.6666666666666666
frozenset({2}) --> frozenset({3}) conf: 0.6666666666666666
frozenset({5}) --> frozenset({2, 3}) conf: 0.6666666666666666
frozenset({3}) --> frozenset({2, 5}) conf: 0.6666666666666666
frozenset({2}) --> frozenset({3, 5}) conf: 0.6666666666666666
rules:  [(frozenset({5}), frozenset({3}), 0.6666666666666666), (frozenset({3}), frozenset({5}), 0.6666666666666666), (frozenset({3}), frozenset({1}), 0.6666666666666666), (frozenset({1}), frozenset({3}), 1.0), (frozenset({5}), frozenset({2}), 1.0), (frozenset({2}), frozenset({5}), 1.0), (frozenset({3}), frozenset({2}), 0.6666666666666666), (frozenset({2}), frozenset({3}), 0.6666666666666666), (frozenset({5}), frozenset({2, 3}), 0.6666666666666666), (frozenset({3}), frozenset({2, 5}), 0.6666666666666666), (frozenset({2}), frozenset({3, 5}), 0.6666666666666666)]

實際應用:發現毒蘑菇的相似特性

實際需求

菌類蘑菇食用對人體有益,現在市場上很受歡迎。假設你在一個山林裡,遇到很多蘑菇,有些可以食用有些有毒。此刻,你或許會詢問山中常駐居民,居民非常友好的告訴你傘菇上有彩色花斑的,樣式好看的等等有毒。他會通過判斷蘑菇的大小,高度,顏色,形狀等23個特徵決定蘑菇有毒,我把將居民的經驗收集在mushromm.dat裡面,以下是部分資料:

1 3 9 13 23 25 34 36 38 40 52 54 59 63 67 76 85 86 90 93 98 107 113 
2 3 9 14 23 26 34 36 39 40 52 55 59 63 67 76 85 86 90 93 99 108 114 
2 4 9 15 23 27 34 36 39 41 52 55 59 63 67 76 85 86 90 93 99 108 115 

其中第一列1代表可以食用2代表有毒。其他各列代表不同特徵。實際中,我們不可能對比23個特徵,我們只需要找出毒蘑菇特有的幾個特徵即可,比如顏色彩色,形狀方形等。我們自然語言描述很容易,就是看到蘑菇,對比下毒蘑菇的幾個特徵,不具備就可以採摘食用了。

到目前為止,我們清楚的採用毒蘑菇共同特徵判斷,那麼如何知道毒蘑菇共同特徵呢?我們就可以使用本節學習的先驗演算法Apriori進行關聯規則找出毒蘑菇的共同特性。

演算法實現

得到資料集

dataSet = [line.split() for line in open("./mushroom.dat").readlines()]

利用我們的先驗演算法計算L頻繁項集和所有元素支援度的全集

L, supportData = apriori(dataSet, minSupport=0.4)

找出關於2的頻繁子項,就知道如果是毒蘑菇,那麼出現頻繁的也可能是毒蘑菇

for item in L[2]:
    if item.intersection('2'):
        print (item)

毒蘑菇的相似特性執行結果

frozenset({'59', '39', '2'})
frozenset({'59', '85', '2'})
frozenset({'34', '39', '2'})
frozenset({'90', '86', '2'})
frozenset({'34', '90', '2'})
frozenset({'39', '86', '2'})
frozenset({'85', '28', '2'})
frozenset({'59', '86&##39;, '2'})
frozenset({'34', '85', '2'})
frozenset({'90', '39', '2'})
frozenset({'39', '85', '2'})
frozenset({'34', '59', '2'})
frozenset({'34', '86', '2'})
frozenset({'90', '59', '2'})
frozenset({'85', '86', '2'})
frozenset({'90', '85', '2'})
frozenset({'63', '85', '2'})

如上結果顯示,遇到如上特徵就很可能是毒蘑菇不能食用的啦。我們上面實驗設定的2-頻繁項集,根據實際需要可以調整k-頻繁項集。

參考文獻

完整程式碼下載

原始碼請進【機器學習和自然語言QQ群:436303759】檔案下載:自然語言處理和機器學習技術QQ交流

作者宣告