Python機器學習算法 — 關聯規則(Apriori、FP-growth)

關聯規則 -- 簡介

        關聯規則挖掘是一種基於規則的機器學習算法,該算法能夠在大數據庫中發現感興趣的關係。它的目的是利用一些度量指標來分辨數據庫中存在的強規則。也便是說關聯規則挖掘是用於知識發現,而非預測,因此是屬於無監督的機器學習方法。
        Apriori算法是一種挖掘關聯規則的頻繁項集算法,其核心思想是經過候選集生成和情節的向下封閉檢測兩個階段來挖掘頻繁項集。

        關聯規則的通常步驟:
              一、找到頻繁集;
              二、在頻繁集中經過可信度篩選得到關聯規則。
        關聯規則應用:
              一、Apriori算法應用普遍,可用於消費市場價格分析,猜想顧客的消費習慣,好比較有名的「尿布和啤酒」的故事;
              二、網絡安全領域中的入侵檢測技術;
              三、可用在用於高校管理中,根據挖掘規則能夠有效地輔助學校管理部門有針對性的開展貧困助學工做;
              四、也可用在移動通訊領域中,指導運營商的業務運營和輔助業務提供商的決策制定。
        關聯規則算法的主要應用是購物籃分析,是爲了從大量的訂單中發現商品潛在的關聯。其中經常使用的一個算法叫Apriori先驗算法。

關聯規則 -- 概念

        關聯分析(Association Analysis):在大規模數據集中尋找有趣的關係。
        頻繁項集(Frequent Item Sets):常常出如今一塊的物品的集合,即包含0個或者多個項的集合稱爲項集。
        支持度(Support):數據集中包含該項集的記錄所佔的比例,是針對項集來講的。
        置信度(Confidence):出現某些物品時,另一些物品一定出現的機率,針對規則而言。
        關聯規則(Association Rules):暗示兩個物品之間可能存在很強的關係。形如A->B的表達式,規則A->B的度量包括支持度和置信度
        項集支持度:一個項集出現的次數與數據集全部事物數的百分比稱爲項集的支持度
        支持度反映了A和B同時出現的機率,關聯規則的支持度等於頻繁集的支持度。
        項集置信度:包含A的數據集中包含B的百分比

        置信度反映了若是交易中包含A,則交易包含B的機率。也能夠稱爲在A發生的條件下,發生B的機率,成爲條件機率。
        只有支持度和置信度(可信度)較高的關聯規則纔是用戶感興趣的。

關聯規則 --支持度和置信度

一、支持度(Support)

        支持度揭示了A與B同時出現的機率。若是A與B同時出現的機率小,說明A與B的關係不大;若是A與B同時出現的很是頻繁,則說明A與B老是相關的。
        支持度: P(A∪B),即A和B這兩個項集在事務集D中同時出現的機率。

二、可信度(Confidence)

        置信度揭示了A出現時,B是否也會出現或有多大機率出現。若是置信度度爲100%,則A和B能夠捆綁銷售了。若是置信度過低,則說明A的出現與B否出現關係不大。
        置信度: P(B|A),即在出現項集A的事務集D中,項集B也同時出現的機率。

三、設定合理的支持度和置信度

        對於某條規則:(A = a)−>(B = b)(support=30%,confident=60%);其中support=30%表示在全部的數據記錄中,同時出現A=a和B=b的機率爲30%;confident=60%表示在全部的數據記錄中,在出現A=a的狀況下出現B=b的機率爲60%,也就是條件機率。支持度揭示了A=a和B=b同時出現的機率,置信度揭示了當A=a出現時,B=b是否會必定出現的機率。
        (1)若是支持度和置信度閉值設置的太高,雖然能夠減小挖掘時間,可是容易形成一些隱含在數據中非頻繁特徵項被忽略掉,難以發現足夠有用的規則;
        (2)若是支持度和置信度閉值設置的太低,又有可能產生過多的規則,甚至產生大量冗餘和無效的規則,同時因爲算法存在的固有問題,會致使高負荷的計算量,大大增長挖掘時間。

關聯規則 -- Apriori算法

Apriori算法簡介

Apriori算法:使用候選項集找頻繁項集
         Apriori算法是一種最有影響的挖掘布爾關聯規則頻繁項集的算法。其核心是基於兩階段頻集思想的遞推算法。該關聯規則在分類上屬於單維、單層、布爾關聯規則。在這裏,全部支持度大於最小支持度的項集稱爲頻繁項集,簡稱頻集。
Apriori原理若是某個項集是頻繁的,那麼它的全部子集也是頻繁的。該定理的逆反定理爲:若是某一個項集是非頻繁的,那麼它的全部超集(包含該集合的集合)也是非頻繁的。Apriori原理的出現,能夠在得知某些項集是非頻繁以後,不須要計算該集合的超集,有效地避免項集數目的指數增加,從而在合理時間內計算出頻繁項集。
在圖中,已知陰影項集{2,3}是非頻繁的。利用這個知識,咱們就知道項集{0,2,3},{1,2,3}以及{0,1,2,3}也是非頻繁的。也就是說,一旦計算出了{2,3}的支持度,知道它是非頻繁的後,就能夠緊接着排除{0,2,3}、{1,2,3}和{0,1,2,3}。

算法思想

        ①找出全部的頻集,這些項集出現的頻繁性至少和預約義的最小支持度同樣。
        ②由頻集產生強關聯規則,這些規則必須知足最小支持度和最小可信度。
        ③使用第1步找到的頻集產生指望的規則,產生只包含集合的項的全部規則,其中每一條規則的右部只有一項,這裏採用的是中規則的定義。
        ④一旦這些規則被生成,那麼只有那些大於用戶給定的最小可信度的規則才被留下來。爲了生成全部頻集,使用了遞推的方法。


算法步驟

Ariori算法有兩個主要步驟:
       
 一、鏈接:(將項集進行兩兩鏈接造成新的候選集)
            利用已經找到的個項的頻繁項集,經過兩兩鏈接得出候選集,注意進行鏈接的,必須有屬性值相同,而後另外兩個不一樣的分別分佈在中,這樣的求出的的候選集。
        
二、剪枝:(去掉非頻繁項集)
             候選集中的並不都是頻繁項集,必須剪枝去掉,越早越好以防止所處理的數據無效項愈來愈多。只有當子集都是頻繁集的候選集纔是頻繁集,這是剪枝的依據

代碼實現node

#Apriori算法實現
from numpy import *

def loadDataSet():
    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

# 獲取候選1項集,dataSet爲事務集。返回一個list,每一個元素都是set集合
def createC1(dataSet):
    C1 = []   # 元素個數爲1的項集(非頻繁項集,由於尚未同最小支持度比較)
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])
    C1.sort()  # 這裏排序是爲了,生成新的候選集時能夠直接認爲兩個n項候選集前面的部分相同
    # 由於除了候選1項集外其餘的候選n項集都是以二維列表的形式存在,因此要將候選1項集的每個元素都轉化爲一個單獨的集合。
    return list(map(frozenset, C1))   #map(frozenset, C1)的語義是將C1由Python列表轉換爲不變集合(frozenset,Python中的數據結構)

# 找出候選集中的頻繁項集
# dataSet爲所有數據集,Ck爲大小爲k(包含k個元素)的候選項集,minSupport爲設定的最小支持度
def scanD(dataSet, Ck, minSupport):
    ssCnt = {}   # 記錄每一個候選項的個數
    for tid in dataSet:
        for can in Ck:
            if can.issubset(tid):
                ssCnt[can] = ssCnt.get(can, 0) + 1   # 計算每個項集出現的頻率
    numItems = float(len(dataSet))
    retList = []
    supportData = {}
    for key in ssCnt:
        support = ssCnt[key] / numItems
        if support >= minSupport:
            retList.insert(0, key)  #將頻繁項集插入返回列表的首部
        supportData[key] = support
    return retList, supportData   #retList爲在Ck中找出的頻繁項集(支持度大於minSupport的),supportData記錄各頻繁項集的支持度

# 經過頻繁項集列表Lk和項集個數k生成候選項集C(k+1)。
def aprioriGen(Lk, k):
    retList = []
    lenLk = len(Lk)
    for i in range(lenLk):
        for j in range(i + 1, lenLk):
            # 前k-1項相同時,纔將兩個集合合併,合併後才能生成k+1項
            L1 = list(Lk[i])[:k-2]; L2 = list(Lk[j])[:k-2]   # 取出兩個集合的前k-1個元素
            L1.sort(); L2.sort()
            if L1 == L2:
                retList.append(Lk[i] | Lk[j])
    return retList

# 獲取事務集中的全部的頻繁項集
# Ck表示項數爲k的候選項集,最初的C1經過createC1()函數生成。Lk表示項數爲k的頻繁項集,supK爲其支持度,Lk和supK由scanD()函數經過Ck計算而來。
def apriori(dataSet, minSupport=0.5):
    C1 = createC1(dataSet)  # 從事務集中獲取候選1項集
    D = list(map(set, dataSet))  # 將事務集的每一個元素轉化爲集合
    L1, supportData = scanD(D, C1, minSupport)  # 獲取頻繁1項集和對應的支持度
    L = [L1]  # L用來存儲全部的頻繁項集
    k = 2
    while (len(L[k-2]) > 0): # 一直迭代到項集數目過大而在事務集中不存在這種n項集
        Ck = aprioriGen(L[k-2], k)   # 根據頻繁項集生成新的候選項集。Ck表示項數爲k的候選項集
        Lk, supK = scanD(D, Ck, minSupport)  # Lk表示項數爲k的頻繁項集,supK爲其支持度
        L.append(Lk);supportData.update(supK)  # 添加新頻繁項集和他們的支持度
        k += 1
    return L, supportData

if __name__=='__main__':
    dataSet = loadDataSet()  # 獲取事務集。每一個元素都是列表
    # C1 = createC1(dataSet)  # 獲取候選1項集。每一個元素都是集合
    # D = list(map(set, dataSet))  # 轉化事務集的形式,每一個元素都轉化爲集合。
    # L1, suppDat = scanD(D, C1, 0.5)
    # print(L1,suppDat)

    L, suppData = apriori(dataSet,minSupport=0.7)
    print(L,suppData)

關聯規則 -- FP樹頻集算法

算法簡介

        在關聯分析中,頻繁項集的挖掘最經常使用到的就是Apriori算法。Apriori算法是一種先產生候選項集再檢驗是否頻繁的「產生-測試」的方法。這種方法有種弊端:當數據集很大的時候,須要不斷掃描數據集形成運行效率很低。 
        而FP-Growth算法就很好地解決了這個問題。它的思路是把數據集中的事務映射到一棵FP-Tree上面,再根據這棵樹找出頻繁項集。FP-Tree的構建過程只須要掃描兩次數據集。 

算法步驟

        FP-growth算法發現頻繁項集的基本過程以下:
                ①構建FP樹;
                ②從FP樹中挖掘頻繁項集;

實現流程

輸入:數據集、最小值尺度 
輸出:FP樹、頭指針表
python

  • 一、遍歷數據集,統計各元素項出現次數,建立頭指針表
  • 二、移除頭指針表中不知足最小值尺度的元素項
  • 三、第二次遍歷數據集,建立FP樹。對每一個數據集中的項集: 
    • 3.1 初始化空FP樹
    • 3.2 對每一個項集進行過濾和重排序
    • 3.3 使用這個項集更新FP樹,從FP樹的根節點開始: 
      • 3.3.1 若是當前項集的第一個元素項存在於FP樹當前節點的子節點中,則更新這個子節點的計數值
      • 3.3.2 不然,建立新的子節點,更新頭指針表
      • 3.3.3 對當前項集的其他元素項和當前元素項的對應子節點遞歸3.3的過程

算法詳解

一、用FP樹編碼數據集算法

        FP-growth算法將數據存儲在一個稱爲FP樹的緊湊數據結構中,它與計算機科學中的其餘樹的結構相似,可是它經過連接來連接類似元素,被連起來的元素能夠看作一個鏈表,以下圖:


 圖1--FP樹的結構示意圖
數據庫

        FP樹會存儲項集出現的頻率,每一個項集都會以路徑的形式存儲在樹中,存在類似元素的集合會共享樹的一部分。只有當集合之間徹底不一樣時樹纔會分叉,樹節點上給出集合中單個元素及其在序列中出現的次數,路徑會給出該序列的出現次數。
        類似項之間的連接即節點連接,用於快速發現類似項的位置,下面的例子:
圖2--用於發現圖1中FP樹的事務數據樣例
        第一列是事務的ID,第二列是事務中的元素項,在圖1中z出現了5次,而{r,z}項支出項了一次,因此z必定本身自己或者和其餘的符號一塊兒出現了4次,而由圖1一樣可知:集合{t,s,y,x,z}出現了2次,集合{t,r,y,x,z}出現了1次,因此z必定自己出現了1次。看了圖2可能會有疑問,爲何在圖1中沒有p,q,w,v等元素呢?這是由於一般會給全部的元素設置一個閾度值(Apriori裏的支持度),低於這個閾值的元素不加以研究。暫時不理解不要緊,看了下面的內容可能會對這一段的內容有比較好的理解。

1.一、構建FP樹安全

        構建FP樹是算法的第一步,在FP樹的基礎之上再對頻繁項集進行挖掘。爲了構建FP樹,要對數據集掃描兩次,第一次對全部元素項出現次數進行計數,記住若是一個元素不是頻繁的,那麼包含這個元素的超集也不是頻繁的,因此不須要考慮這些超集,第二遍的掃描只考慮那些頻繁元素。
        除了圖1給出的FP樹以外,還須要一個頭指針表來指向給定類型的第一個實例。利用頭指針表能夠快速訪問FP樹中一個給定類型的全部元素,發現類似元素項,以下圖所示:
圖3--帶頭指針的FP樹
        頭指針表的數據結構是字典,除了存放頭指針元素以外,還能夠存放FP中每類元素的個數。第一次遍歷數據集獲得每一個元素項出現的頻率,接下來去掉不知足最小值支持度的元素項,在接下來就能夠建立FP樹了,構建時,將每一個項集添加到一個已經存在的路徑中,若是該路徑不存在,則建立一個新的路徑。每一個事務都是一個無序的集合,然而在FP樹中相同項只會出現一次,{x,y,z}和{y,z,x}應該在同一個路徑上,因此在將集合添加到樹以前要對每一個集合進行排序,排序是基於各個元素出現的頻率來進行的,使用圖3頭指針表中單個元素的出現值,對圖2中的數據進行過濾,重排後的新數據以下:


圖4--移除非頻繁項,從新排序後的事務表
網絡

        如今,就能夠構建FP樹了,從空集開始,向其中不斷添加頻繁項集。過濾,排序後的事務依次添加到樹中,若是樹中已有現有元素,則增長該元素的值;若是元素不存在,則添加新分枝。圖4中事務表前兩條事務添加的過程以下圖所示:


圖5--FP構建過程示例圖
數據結構

二、從FP樹中挖掘頻繁項

        有了FP樹以後就能夠抽取頻繁項集了,思想與Apriori算法大體同樣,從單元素項集開始,逐步構建更大的集合,只不過不須要原始的數據集了。
        從FP樹中抽取頻繁項集的三個基本步驟:
            1)從FP樹中得到條件模式基;
            2)利用條件模式基,構建一個條件FP樹;
            3)迭代重複步驟(1)(2)直到樹只包含一個元素項爲止

2.1抽取條件模式基

        條件模式基是以所查找元素項爲結尾的路徑集合,每一條路徑包含一條前綴路徑和結尾元素,圖3中,符號r的前綴路徑有{x,s}、{z,x,y}和{z},每一條前綴路徑都與一個數據值關聯,這個值等於路徑上r的數目,下表中列出單元素頻繁項的全部前綴路徑。

圖6--每一個頻繁項的前綴路徑
app

        前綴路徑將被用於構建條件FP樹。爲了得到這些路徑,能夠對FP樹進行窮舉式搜索,直到得到想要的頻繁項爲止,但可使用一個更爲有效的方法加速搜索過程。能夠用先前的頭指針表來建立一種更爲有效的方法,頭指針表中包含相同類型元素鏈表的起始指針。一旦達了每個元素項,就能夠上溯這棵樹直到根節點爲止。

2.二、建立條件FP樹

        對於每個頻繁項,都要建立一個條件FP樹,將上面的條件模式基做爲輸入,經過相同的建樹方法來構建這些條件樹,而後遞歸地發現頻繁項、發現條件模式基,以及發現另外的條件樹。舉個例子,假定爲頻繁項t建立一個條件FP樹,而後對{t,y},{t,x}、...重複該過程。元素項t的條件FP樹的構建過程以下:


圖7機器學習

       s,r雖然是條件模式基的一部分,且單獨看都是頻繁項,可是在t的條件樹中,他倒是不頻繁的,分別出現了2次和一次,小於閾值3,因此{t,r},{t,s}不是頻繁的。接下來對集合{t,z},{t,x},{t,y}來挖掘對應的條件樹,會產生更復雜的頻率項集,該過程重複進行,直到條件樹中沒有元素 爲止,中止。
代碼實現
# FP樹類
class treeNode:
    def __init__(self, nameValue, numOccur, parentNode):
        self.name = nameValue  #節點元素名稱,在構造時初始化爲給定值
        self.count = numOccur   # 出現次數,在構造時初始化爲給定值
        self.nodeLink = None   # 指向下一個類似節點的指針,默認爲None
        self.parent = parentNode   # 指向父節點的指針,在構造時初始化爲給定值
        self.children = {}  # 指向子節點的字典,以子節點的元素名稱爲鍵,指向子節點的指針爲值,初始化爲空字典

    # 增長節點的出現次數值
    def inc(self, numOccur):
        self.count += numOccur

    # 輸出節點和子節點的FP樹結構
    def disp(self, ind=1):
        print(' ' * ind, self.name, ' ', self.count)
        for child in self.children.values():
            child.disp(ind + 1)

# =======================================================構建FP樹==================================================

# 對不是第一個出現的節點,更新頭指針塊。就是添加到類似元素鏈表的尾部
def updateHeader(nodeToTest, targetNode):
    while (nodeToTest.nodeLink != None):
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode

# 根據一個排序過濾後的頻繁項更新FP樹
def updateTree(items, inTree, headerTable, count):
    if items[0] in inTree.children:
        # 有該元素項時計數值+1
        inTree.children[items[0]].inc(count)
    else:
        # 沒有這個元素項時建立一個新節點
        inTree.children[items[0]] = treeNode(items[0], count, inTree)
        # 更新頭指針表或前一個類似元素項節點的指針指向新節點
        if headerTable[items[0]][1] == None:  # 若是是第一次出現,則在頭指針表中增長對該節點的指向
            headerTable[items[0]][1] = inTree.children[items[0]]
        else:
            updateHeader(headerTable[items[0]][1], inTree.children[items[0]])

    if len(items) > 1:
        # 對剩下的元素項迭代調用updateTree函數
        updateTree(items[1::], inTree.children[items[0]], headerTable, count)

# 主程序。建立FP樹。dataSet爲事務集,爲一個字典,鍵爲每一個事物,值爲該事物出現的次數。minSup爲最低支持度
def createTree(dataSet, minSup=1):
    # 第一次遍歷數據集,建立頭指針表
    headerTable = {}
    for trans in dataSet:
        for item in trans:
            headerTable[item] = headerTable.get(item, 0) + dataSet[trans]
    # 移除不知足最小支持度的元素項
    keys = list(headerTable.keys()) # 由於字典要求在迭代中不能修改,因此轉化爲列表
    for k in keys:
        if headerTable[k] < minSup:
            del(headerTable[k])
    # 空元素集,返回空
    freqItemSet = set(headerTable.keys())
    if len(freqItemSet) == 0:
        return None, None
    # 增長一個數據項,用於存放指向類似元素項指針
    for k in headerTable:
        headerTable[k] = [headerTable[k], None]  # 每一個鍵的值,第一個爲個數,第二個爲下一個節點的位置
    retTree = treeNode('Null Set', 1, None) # 根節點
    # 第二次遍歷數據集,建立FP樹
    for tranSet, count in dataSet.items():
        localD = {} # 記錄頻繁1項集的全局頻率,用於排序
        for item in tranSet:
            if item in freqItemSet:   # 只考慮頻繁項
                localD[item] = headerTable[item][0] # 注意這個[0],由於以前加過一個數據項
        if len(localD) > 0:
            orderedItems = [v[0] for v in sorted(localD.items(), key=lambda p: p[1], reverse=True)] # 排序
            updateTree(orderedItems, retTree, headerTable, count) # 更新FP樹
    return retTree, headerTable

# =================================================查找元素條件模式基===============================================

# 直接修改prefixPath的值,將當前節點leafNode添加到prefixPath的末尾,而後遞歸添加其父節點。
# prefixPath就是一條從treeNode(包括treeNode)到根節點(不包括根節點)的路徑
def ascendTree(leafNode, prefixPath):
    if leafNode.parent != None:
        prefixPath.append(leafNode.name)
        ascendTree(leafNode.parent, prefixPath)

# 爲給定元素項生成一個條件模式基(前綴路徑)。basePet表示輸入的頻繁項,treeNode爲當前FP樹中對應的第一個節點
# 函數返回值即爲條件模式基condPats,用一個字典表示,鍵爲前綴路徑,值爲計數值。
def findPrefixPath(basePat, treeNode):
    condPats = {}  # 存儲條件模式基
    while treeNode != None:
        prefixPath = []  # 用於存儲前綴路徑
        ascendTree(treeNode, prefixPath)  # 生成前綴路徑
        if len(prefixPath) > 1:
            condPats[frozenset(prefixPath[1:])] = treeNode.count  # 出現的數量就是當前葉子節點的數量
        treeNode = treeNode.nodeLink  # 遍歷下一個相同元素
    return condPats

# =================================================遞歸查找頻繁項集===============================================
# 根據事務集獲取FP樹和頻繁項。
# 遍歷頻繁項,生成每一個頻繁項的條件FP樹和條件FP樹的頻繁項
# 這樣每一個頻繁項與他條件FP樹的頻繁項都構成了頻繁項集

# inTree和headerTable是由createTree()函數生成的事務集的FP樹。
# minSup表示最小支持度。
# preFix請傳入一個空集合(set([])),將在函數中用於保存當前前綴。
# freqItemList請傳入一個空列表([]),將用來儲存生成的頻繁項集。
def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
    # 對頻繁項按出現的數量進行排序進行排序
    sorted_headerTable = sorted(headerTable.items(), key=lambda p: p[1][0])  #返回從新排序的列表。每一個元素是一個元組,[(key,[num,treeNode],())
    bigL = [v[0] for v in sorted_headerTable]  # 獲取頻繁項
    for basePat in bigL:
        newFreqSet = preFix.copy()  # 新的頻繁項集
        newFreqSet.add(basePat)     # 當前前綴添加一個新元素
        freqItemList.append(newFreqSet)  # 全部的頻繁項集列表
        condPattBases = findPrefixPath(basePat, headerTable[basePat][1])  # 獲取條件模式基。就是basePat元素的全部前綴路徑。它像一個新的事務集
        myCondTree, myHead = createTree(condPattBases, minSup)  # 建立條件FP樹

        if myHead != None:
            # 用於測試
            print('conditional tree for:', newFreqSet)
            myCondTree.disp()
            mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)  # 遞歸直到再也不有元素

# 生成數據集
def loadSimpDat():
    simpDat = [['r', 'z', 'h', 'j', 'p'],
               ['z', 'y', 'x', 'w', 'v', 'u', 't', 's'],
               ['z'],
               ['r', 'x', 'n', 'o', 's'],
               ['y', 'r', 'x', 'z', 'q', 't', 'p'],
               ['y', 'z', 'x', 'e', 'q', 's', 't', 'm']]
    return simpDat

# 將數據集轉化爲目標格式
def createInitSet(dataSet):
    retDict = {}
    for trans in dataSet:
        retDict[frozenset(trans)] = 1
    return retDict

if __name__=='__main__':
    minSup =3
    simpDat = loadSimpDat()  # 加載數據集
    initSet = createInitSet(simpDat)  # 轉化爲符合格式的事務集
    myFPtree, myHeaderTab = createTree(initSet, minSup)  # 造成FP樹
    # myFPtree.disp()  # 打印樹

    freqItems = []  # 用於存儲頻繁項集
    mineTree(myFPtree, myHeaderTab, minSup, set([]), freqItems)  # 獲取頻繁項集
    print(freqItems)  # 打印頻繁項集
相關文章
相關標籤/搜索