機器學習實戰(十)Apriori算法進行關聯分析

一、前言

      在去雜貨店買東西的過程,實際包含了許多機器學習的當前及未來應用,這包括物品的展示方式、購物之後優惠券的提供以及用戶忠誠度計劃,等等。它們都離不開對大量數據的分析。
      通過查看哪些商品經常在一起購買,可以幫助商店瞭解用戶的購買行爲。這種從數據海洋中抽取的知識可以用於商品定價、市場促銷、存貨管理等環節。從大規模數據集中尋找物品間的隱含關係被稱作關聯分析(association analysis ) 或者關聯規則學習(association rule learning)。這裏的主要問題在於,尋找物品的不同組合是一項十分耗時的任務,所需的計算代價很高,蠻力搜索方法並不能解決這個問題,所以需要用更智能的方法在合理的時間範圍內找到頻繁項集。
      下面首先詳細討論關聯分析,然後討論Apriori原 理,Apriori算法正是基於該原理得到的。接下來創建函數頻繁項集髙效發現的函數,然後從頻繁項集中抽取出關聯規則。

1.1 關聯分析

Apriori算法
優點:易編碼實現。’
缺點:在大數據集上可能較慢。
適用數據類型:數值型或者標稱型數據。

關聯分析是一種在大規模數據集中尋找有趣關係的任務。這些關係可以有兩種形式:頻繁項集或者關聯規則頻 繁 項 集 (frequent item sets)是經常出現在一塊的物品的集合關聯規則(association rules )暗示兩種物品之間可能存在很強的關係。下面會用一個例子來說明這兩種概念。圖11-1給出了某個雜貨店的交易清單。   
                              這裏寫圖片描述

頻繁項集是指那些經常出現在一起的物品集合,圖11-1中的集合{葡萄酒,尿布,豆奶丨就是頻繁項集的一個例子(回想一下,集合是由一對大括號“{ }”來表示的)。從下面的數據集中也可以找到諸如尿布―葡萄酒的關聯規則。這意味着如果有人買了尿布,那麼他很可能也會買葡萄酒

應該如何定義這些有趣的關係?誰來定義什麼是有趣?當尋找頻繁項集時,頻 繁(frequent) 的定義是什麼?有許多概念可以解答上述問題,不過其中最重要的是支持度和可信度

一個項集的支持度(support)被定義爲數據集中包含該項集的記錄所佔的比例。從圖11-1中可以得到,{豆奶)的支持度爲4/5。而在5條交易記錄中有3條包含{豆奶,尿布} , 因此{豆奶,尿布}的支持度爲3/5。支持度是針對項集來說的,因此可以定義一個最小支持度,而只保留滿足最小支持度的項集。

可信度或置信度(confidence)是針對一條諸如{尿布}->{葡萄酒}的關聯規則來定義的。這條規則的可信度被定義爲“支持度({尿布,葡萄酒})/支持度({尿布})”。從圖11-1中可以看到,由於 {尿布,葡萄酒}的支持度爲3/5,尿布的支持度爲4/5,所以“尿 布 — 葡萄酒”的可信度爲3/4=0.75。這意味着對於包含“尿布”的所有記錄,我們的規則對其中75%的記錄都適用

支持度和可信度是用來量化關聯分析是否成功的方法。

1.2 Apriori原理


假設我們在經營一家商品種類並不多的雜貨店,我們對那些經常在一起被購買的商品非常感興趣。我們只有4種商品:商品0,商品1,商品2和商品3。那麼所有可能被一起購買的商品組合都有哪些?這些商品組合可能只有一種商品,比如商品0 , 也可能包括兩種、三種或者所有四種商品。我們並不關心某人買了兩件商品0以及四件商品2的情況,我們只關心他購買了一種或多種商品。(典型的高中的組合問題,C1N+C2N+...CNNCN1+CN2+...CNN,總共爲2N−1種組合2N−1種組合)

Apriori算法的一般過程
⑴ 收集數據:使用任意方法。
(2)準備數據:任何數據類型都可以,因爲我們只保存集合。
(3)分析數據:使用任意方法。
(4)訓練算法:使用Apriori算法來找到頻繁項集。
(5)測試算法:不需要測試過程。
(6)使用算法:用於發現頻繁項集以及物品之間的關聯規則。

圖11-2顯示了物品之間所有可能的組合。爲了讓該圖更容易懂,圖中使用物品的編號0來取代物品0本身。另外,圖中從上往下的第一個集合是∅∅,表示空集或不包含任何物品的集合。物品集合之間的連線表明兩個或者更多集合可以組合形成一個更大的集合。
前面說過,我們的目標是找到經常在一起購買的物品集合。而在11.1節中,我們使用集合的支持度來度量其出現的頻率。一個集合的支持度是指有多少比例的交易記錄包含該集合。如何對一個給定的集合,比如{0,3},來計算其支持度?我們遍歷毎條記錄並檢查該記錄包含0和3,如果記錄確實同時包含這兩項,那麼就增加總計數值。在掃描完所有數據之後,使用統計得到的總數除以總的交易記錄數,就可以得到支持度。上述過程和結果只是針對單個集合{0,3} 。要獲得每種可能集合的支持度就需要多次重複上述過程。我們可以數一下圖11-2中的集合數目,會發現即使對於僅有4種物品的集合,也需要遍歷數據15次。而隨着物品數目的增加遍歷次數會急劇增長。對於包含— 物品的數據集共有2N−12N−1種項集組合。事實上,出售10 000或更多種物品的商店並不少見。即使只出售100種商品的商店也會有1.26∗10301.26∗1030種可能的項集組合。對於現代的計算機而言,需要很長的時間才能完成運算。
                                 這裏寫圖片描述

爲了降低所需的計算時間,研究人員發現一種所謂的Apriori原理。Apriori原理可以幫我們減少可能感興趣的項集。Apri0ri原理是說如果某個項集是頻繁的,那麼它的所有子集也是頻繁的。對於圖11-2給出的例子,這意味着如果{0,1}是頻繁的,那麼{0}、{1}也一定是頻繁的。這個原理直觀上並沒有什麼幫助,但是如果反過來看就有用了,也就是說如果一個項集是非頻繁集,那麼它的所有超集也是非頻繁的(如圖11-3所示)(很容易理解,如果一個項集爲非頻繁集,那麼它的支持度小於給定最小支持度,那麼它的所有超集也必定小於最小支持度,因此它的所有超集也是非頻繁的)。

在 圖11-3中 ,已知陰影項集{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.3 使用Apriori算法來發現頻繁集


1.1節提到,關聯分析的目標包括兩項:發現頻繁項集和發現關聯規則。首先需要找到頻繁項集,然後才能獲得關聯規則。本節將只關注於發現頻繁項集。
Apriori 算法是發現頻繁項集的一種方法。Apriori算法的兩個輸人蔘數分別是最小支持度和數據集。該算法首先會生成所有單個物品的項集列表。接着掃描交易記錄來查看哪些項集滿足最小支持度要求,那些不滿足最小支持度的集合會被去掉。然 後 ,對剩下來的集合進行組合以生成包含兩個元素的項集。接下來,再重新掃描交易記錄,去掉不滿足最小支持度的項集。該過程重複進行直到所有項集都被去掉。

1)生成候選項集
在使用Python對整個程序編碼之前,需要創建一些輔助函數。下面會創建一個用於構建初始集合的函數,也會創建一個通過掃描數據集以尋找交易記錄子集的函數。數據集掃描的僞代碼大致如下:
對數據集中的每條交易記錄tran
  對每個候選項集can:
    檢查一下can是否是tran的 子 集 :
    如果是,則增加can的計數值
對每個候選項集:
如果其支持度不低於最小值,則保留該項集
返回所有頻繁項集列表

Apriori算法中的輔助函數,代碼如下:
 



def loadDataSet():
    """
    加載數據集
    :return: 列表的列表
    """
    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

#生成所有單個物品的項集列表
def createC1(dataSet):
    """
    :param dataSet:
    :return:
    """
    C1 = []
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])
    C1.sort()  # 對 C1 進行降序排序
    # 將C1中的每個集合映射爲frozenset,之後可以作爲字典的鍵,筆者使用的是python3,所以將書中的map(frozenset,C1)替換爲了list(map(frozenset,C1))
    return list(map(frozenset, C1))


def scanD(D, Ck, minSupport):
    ssCnt = {} # 定義一個字典
    # 對數據集中的每個樣本,對候選項集中的每個集合,如果該集合爲樣本子集,那麼對應計數+1(注意使用了字典,這個時候frozenset就起到用處了,不然會報錯)
    for tid in D:
        for can in Ck:
            if can.issubset(tid):
                if not can in ssCnt :
                    ssCnt[can] = 1
                else:
                    ssCnt[can] += 1
    numItems = float(len(D))
    retList = []
    supportData = {}
    # 遍歷ssCnt字典,找出滿足最小支持度的候選項集,將其添加到retList中,並且將項集的支持度保存在supportData字典中,之後求關聯關係的時候會用到
    for key in ssCnt:
        support = ssCnt[key] / numItems
        if support >= minSupport:
            retList.insert(0, key)
            # 書中這一行在if塊外面,我認爲應該放在裏面,只取頻繁項集的支持度,而且經過測試,確實與放在外面效果完全一樣。
            supportData[key] = support
    return retList, supportData


if __name__ == '__main__':
    dataSet=loadDataSet()
    Ck=createC1(dataSet)
    retList, supportData=scanD(dataSet,Ck,0.5)
    print('%s\n%s'%(retList,supportData))

2)組織完整的Apriori算法

整個Apriori算法的僞代碼如下:
當集合中項的個數大於0時
構建一個k個項組成的候選項集的列表
檢查數據以確認每個項集都是頻繁的
保留頻繁項集並構建k+1項組成的候選項集的列表



def loadDataSet():
    """
    加載數據集
    :return: 列表的列表
    """
    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

#生成所有單個物品的項集列表
def createC1(dataSet):
    """
    :param dataSet:
    :return:
    """
    C1 = []
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])
    C1.sort()  # 對 C1 進行降序排序
    # 將C1中的每個集合映射爲frozenset,之後可以作爲字典的鍵,筆者使用的是python3,所以將書中的map(frozenset,C1)替換爲了list(map(frozenset,C1))
    return list(map(frozenset, C1))


def scanD(D, Ck, minSupport):
    ssCnt = {} # 定義一個字典
    # 對數據集中的每個樣本,對候選項集中的每個集合,如果該集合爲樣本子集,那麼對應計數+1(注意使用了字典,這個時候frozenset就起到用處了,不然會報錯)
    for tid in D:
        for can in Ck:
            if can.issubset(tid):
                if not can in ssCnt :
                    ssCnt[can] = 1
                else:
                    ssCnt[can] += 1
    numItems = float(len(D))
    retList = []
    supportData = {}
    # 遍歷ssCnt字典,找出滿足最小支持度的候選項集,將其添加到retList中,並且將項集的支持度保存在supportData字典中,之後求關聯關係的時候會用到
    for key in ssCnt:
        support = ssCnt[key] / numItems
        if support >= minSupport:
            retList.insert(0, key)
            # 書中這一行在if塊外面,我認爲應該放在裏面,只取頻繁項集的支持度,而且經過測試,確實與放在外面效果完全一樣。
            supportData[key] = support
    return retList, supportData


# 創造k個項組成的候選項集列表的函數
# creates Ck
def aprioriGen(Lk, k):
    retList = []
    lenLk = len(Lk)
    for i in range(lenLk):
        for j in range(i + 1, lenLk):
            # 如果這兩個集合的前面k-2個元素都相等,那麼就將這兩個集合合成一個大小爲k的集合
            L1 = list(Lk[i])[:k - 2];
            L2 = list(Lk[j])[:k - 2]
            L1.sort();
            L2.sort()
            if L1 == L2:  # if first k-2 elements are equal
                # set union
                retList.append(Lk[i] | Lk[j])
    return retList


def apriori(dataSet, minSupport=0.5):
    # 生成所有單個物品的項集列表
    C1 = createC1(dataSet)
    # 將數據集映射爲set的集合,筆者使用的是python3,所以與書中代碼略有不同
    D = list(map(set, dataSet))
    # 調用scanD,找出支持度>=C1的項集
    L1, supportData = scanD(D, C1, minSupport)
    # L存儲所有頻繁項集
    L = [L1]
    # K爲用aprioriGen創建的項集的項數
    k = 2
    # 迭代L,直到L[k-1]爲空
    while (len(L[k - 2]) > 0):
        Ck = aprioriGen(L[k - 2], k)
        Lk, supK = scanD(D, Ck, minSupport)  # scan DB to get Lk
        supportData.update(supK)
        L.append(Lk)
        k += 1
    return L, supportData

if __name__ == '__main__':
    dataSet=loadDataSet()
    Ck=createC1(dataSet)
    retList, supportData=scanD(dataSet,Ck,0.5)
    # print('%s\n%s'%(retList,supportData))
    L, supportData=apriori(dataSet)
    print('%s\n%s'%(L,supportData))

結果顯示:

{frozenset({1}): 0.5, frozenset({3}): 0.75, frozenset({2}): 0.75, frozenset({5}): 0.75, frozenset({1, 3}): 0.5, frozenset({2, 5}): 0.75, frozenset({3, 5}): 0.5, frozenset({2, 3}): 0.5, frozenset({2, 3, 5}): 0.5}

1.4 從頻繁項集中挖掘關聯規則

      要找到關聯規則,我們首先從一個頻繁項集開始。我們知道集合中的元素是不重複的,但我們想知道基於這些元素能否獲得其他內容。某個元素或者某個元素集合可能會推導出另一個元素。從雜貨店的例子可以得到,如果有一個頻繁項集{豆奶,萵苣 },那麼就可能有一條關聯規則”豆奶 — 萵苣”。這意味着如果有人購買了豆奶,那麼在統計上他會購買萵苣的概率較大。但是,這一條反過來並不總是成立。也就是說,即 使 “豆 奶 — 萵苣”統計上顯著,那 麼 “萵苣 —豆奶”也不一定成立。(從邏輯研究上來講,箭頭左邊的集合稱作前件,箭頭右邊的集合稱爲後件。)

      1.3節給出了頻繁項集的量化定義,即它滿足最小支持度要求。對於關聯規則,我們也有類似的量化方法,這種量化指標稱爲可信度。一條規則P->H的可信度定義爲support(P|H)/support(P),記住,在Python中,操作符丨表示集合的並操作,而數學上集合並的符號是U。P | H是指所有出現在集合P或者集合H 中的元素。

      從一個頻繁項集中可以產生多少條關聯規則?圖11-4的網格圖給出的是從項集{0,1,2,3}產生的所有關聯規則。爲找到感興趣的規則,我們先生成一個可能的規則列表,然後測試每條規則的可信度。如果可信度不滿足最小要求,則去掉該規則。

                      這裏寫圖片描述

類似於上一節的頻繁項集生成,我們可以爲每個頻繁項集產生許多關聯規則。如果能夠減少規則數目來確保問題的可解性,那麼計算起來就會好很多。可以觀察到,如果某條規則並不滿足最小可信度要求,那麼該規則的所有子集也不會滿足最小可信度要求。以圖11-4爲例,假設規則{0,1,2}->{3}並不滿足最小可信度要求,那麼就知道任何左部爲{0,1,2}子集的規則也不會滿足最小可信度要求。在圖11-4中這些規則上都加了陰影來表示。(其實用後件來說比較好理解,如果某條規則並不滿足最小可信度要求,那麼以該規則的後件的所有超集爲後件的規則也不會滿足最小可信度,而這也導致了求關聯規則跟之前求頻繁項集的方法類似。p(頻繁項集)不變,以該規則的後件的所有超集爲後件的規則的前件爲該規則的子集,因此p(前件)增大,分子不變,分母增大)

可以利用關聯規則的上述性質屬性來減少需要測試的規則數目。類似於程序清單11-2中的Apriori算法,可以首先從一個頻繁項集開始,接着創建一個規則列表,其中規則右部只包含一個元素,然後對這些規則進行測試。接下來合併所有剩餘規則來創建一個新的規則列表,其中規則右部包含兩個元素。這種方法也被稱作分級法。

關聯規則生成函數,代碼如下;



def loadDataSet():
    """
    加載數據集
    :return: 列表的列表
    """
    return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]

#生成所有單個物品的項集列表
def createC1(dataSet):
    """
    :param dataSet:
    :return:
    """
    C1 = []
    for transaction in dataSet:
        for item in transaction:
            if not [item] in C1:
                C1.append([item])
    C1.sort()  # 對 C1 進行降序排序
    # 將C1中的每個集合映射爲frozenset,之後可以作爲字典的鍵,筆者使用的是python3,所以將書中的map(frozenset,C1)替換爲了list(map(frozenset,C1))
    return list(map(frozenset, C1))


def scanD(D, Ck, minSupport):
    ssCnt = {} # 定義一個字典
    # 對數據集中的每個樣本,對候選項集中的每個集合,如果該集合爲樣本子集,那麼對應計數+1(注意使用了字典,這個時候frozenset就起到用處了,不然會報錯)
    for tid in D:
        for can in Ck:
            if can.issubset(tid):
                if not can in ssCnt :
                    ssCnt[can] = 1
                else:
                    ssCnt[can] += 1
    numItems = float(len(D))
    retList = []
    supportData = {}
    # 遍歷ssCnt字典,找出滿足最小支持度的候選項集,將其添加到retList中,並且將項集的支持度保存在supportData字典中,之後求關聯關係的時候會用到
    for key in ssCnt:
        support = ssCnt[key] / numItems
        if support >= minSupport:
            retList.insert(0, key)
            # 書中這一行在if塊外面,我認爲應該放在裏面,只取頻繁項集的支持度,而且經過測試,確實與放在外面效果完全一樣。
            supportData[key] = support
    return retList, supportData


# 創造k個項組成的候選項集列表的函數
# creates Ck
def aprioriGen(Lk, k):
    retList = []
    lenLk = len(Lk)
    for i in range(lenLk):
        for j in range(i + 1, lenLk):
            # 如果這兩個集合的前面k-2個元素都相等,那麼就將這兩個集合合成一個大小爲k的集合
            L1 = list(Lk[i])[:k - 2];
            L2 = list(Lk[j])[:k - 2]
            L1.sort();
            L2.sort()
            if L1 == L2:  # if first k-2 elements are equal
                # set union
                retList.append(Lk[i] | Lk[j])
    return retList


def apriori(dataSet, minSupport=0.5):
    # 生成所有單個物品的項集列表
    C1 = createC1(dataSet)
    # 將數據集映射爲set的集合,筆者使用的是python3,所以與書中代碼略有不同
    D = list(map(set, dataSet))
    # 調用scanD,找出支持度>=C1的項集
    L1, supportData = scanD(D, C1, minSupport)
    # L存儲所有頻繁項集
    L = [L1]
    # K爲用aprioriGen創建的項集的項數
    k = 2
    # 迭代L,直到L[k-1]爲空
    while (len(L[k - 2]) > 0):
        Ck = aprioriGen(L[k - 2], k)
        Lk, supK = scanD(D, Ck, minSupport)  # scan DB to get Lk
        supportData.update(supK)
        L.append(Lk) # 存儲頻繁項集
        k += 1
    return L, supportData

#supportData is a dict coming from scanD
def generateRules(L, supportData, minConf=0.7):
    bigRuleList = []
    #only get the sets with two or more items
    #注意,i從1開始,表示只取項數大於等於2的項集
    for i in range(1, len(L)):
        for freqSet in L[i]:
            #對每個頻繁項集集合的頻繁項集,生成單項集合,注意使用了frozenset,因爲之後要用[item]作爲key獲取支持度
            H1 = [frozenset([item]) for item in freqSet]
            #項數多於2,調用rulesFromConseq
            if (i > 1):
                rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
            #項數等於2,調用calcConf
            else:
                calcConf(freqSet, H1, supportData, bigRuleList, minConf)
    return bigRuleList

def calcConf(freqSet, H, supportData, brl, minConf=0.7):
    #create new list to return
    #存儲滿足最小可信度的規則的後件集合,以便之後再次合併時使用
    prunedH = []
    #對候選項集H進行迭代,選擇出符合最小可信度的關聯規則
    for conseq in H:
        #因爲關聯規則每次都是對一個項集而言,因此直接用項集與後件做差,就可以得出前件
        conf = supportData[freqSet]/supportData[freqSet-conseq] #calc confidence
        print(freqSet - conseq,freqSet,supportData[freqSet-conseq],supportData[freqSet])
        if conf >= minConf:
            print(freqSet-conseq,'-->',conseq,'conf:',conf)
            brl.append((freqSet-conseq, conseq, conf))
            prunedH.append(conseq)
    return prunedH

def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
    #獲取候選項集項數
    m = len(H[0])
    #try further merging
    if (len(freqSet) > (m + 1)):
        #create Hm+1 new candidates
        #百思不得起解,如果直接合並,那麼對於項數大於等於3的項集的後件只能大於等於2,而沒有1的情況,可能是由於項數爲2時已經可以得出所有項數爲1的後件??
        Hmp1 = aprioriGen(H, m+1)
        #得出滿足最小可信度的候選關聯規則
        Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
        if (len(Hmp1) > 1):    #need at least two sets to merge
        #如果滿足最小可信度的候選關聯規則數目大於1,那麼遞歸,將項數+1,繼續進行過濾,直到候選關聯規則數目小於等於1或者freqSet數目<=m+1,例如{1,2,3}不能以{1,2,3}爲後件
            rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)


if __name__ == '__main__':
    dataSet=loadDataSet()
    # 生成只有一個元素的集合
    Ck=createC1(dataSet)
    # 根據給定的支持度獲取只有一個頻繁項集
    retList, supportData=scanD(dataSet,Ck,0.5)
    # 獲取最小支持度是0.5 多個元素頻繁項集的集合
    L, supportData=apriori(dataSet)
    print('%s'%(supportData))
    generateRules(L,supportData,minConf=0.7)

結果顯示:

調整可信度閾值爲0.5,截圖如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章