關聯規則方法之apriori演算法

伯納烏掃地老僧發表於2018-08-23

        Apriori algorithm是關聯規則裡一項基本演算法,是由Rakesh Agrawal和Ramakrishnan Srikant兩位博士在1994年提出的關聯規則挖掘演算法。關聯規則的目的就是在一個資料集中找出項與項之間的關係,也被稱為購物藍分析 (Market Basket analysis),因為“購物藍分析”很貼切的表達了適用該演算法情景中的一個場景。 

        先上一下apriori的python實現的程式碼:

#! -*- coding:utf-8 -*-
#code write by guohao

import itertools

class Apriori(object):
    
    def __init__(self,min_sup=0.2,dataDic={}):
        self.data = dataDic      #構建資料記錄詞典
        self.size = len(dataDic) #統計事務個數
        self.min_sup = min_sup   #最小支援度閾值
        self.min_sup_val = min_sup * self.size  #最小支援度計數

    def find_frequent_1_itemsets(self):
        FreqDic = {}   #用於統計物品的支援度計數
        for event in self.data:    #event為每一條記錄
            for item in self.data[event]:   #item為項
                if item in FreqDic:
                    FreqDic[item] += 1
                else:
                    FreqDic[item] = 1

        L1 = []
        for itemset in FreqDic:
            if FreqDic[itemset] >=self.min_sup_val:  #過濾掉小於最小支援度計數的1-項集
                L1.append([itemset])
        return L1

    def has_infrequent_subset(self,c,L_last,k):
        #c為當前集合,L_last為上一個頻繁項集的集合,k為當前頻繁項集內的元素個數
        #該函式用於檢查當前子集是否都為頻繁項集
        subsets = list(itertools.combinations(c,k-1))
        for each in subsets:
            each = list(each)
            if each not in L_last:
                return True
        return False

    def apriori_gen(self,L_last):  #連線步實現
        k = len(L_last[0]) + 1
        Ck = []
        for itemset1 in L_last:
            for itemset2 in L_last:
                flag = 0
                for i in range(k-2):
                    if itemset1[i] != itemset2[i]:    #如果前k-2項中有一項不相等,則合併的項集必定不為頻繁項集
                        flag = 1
                        break
                if flag == 1:continue
                if itemset1[k-2] < itemset2[k-2]:
                    c = itemset1 + [itemset2[k-2]]    #合成待定k項集
                else:
                    continue

                if self.has_infrequent_subset(c,L_last,k):  #判斷是否為1-項集
                    continue
                else:
                    Ck.append(c)
        return Ck

    def do(self):
        L_last = self.find_frequent_1_itemsets()   #找到頻繁一項集
        L = L_last
        i = 0
        while L_last != []:
            Ck = self.apriori_gen(L_last)      #合併形成新的待定頻繁項集
            FreqDic = {}         #項集的頻數集合
            for event in self.data:
                for c in Ck:    
                    if set(c) <= set(self.data[event]): #判斷新合成的頻繁集是否是事務記錄的子集
                        if tuple(c) in FreqDic:
                            FreqDic[tuple(c)] += 1
                        else:
                            FreqDic[tuple(c)] = 1
            Lk = []
            for c in FreqDic:
                if FreqDic[c] > self.min_sup_val: #判斷新形成的待定頻繁項集是否為為真正的頻繁項集
                    Lk.append(list(c))
            L_last = Lk
            L += Lk
        return L



#*****************************Test*****************************
print('======================Test===================')
Data = {'T100':['I1','I2','I5'],
        'T200':['I2','I4'],
        'T300':['I2','I3'],
        'T400':['I1','I2','I4'],
        'T500':['I1','I3'],
        'T600':['I2','I3'],
        'T700':['I1','I3'],
        'T800':['I1','I2','I3','I5'],
        'T900':['I1','I2','I3']}

a = Apriori(dataDic = Data)
print(a.do())

        apriori演算法的核心是連線步與剪下步,要想了解具體的演算法思想以及連線步剪下步原理的話,可以看一下這篇文章

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

        這裡解釋一下程式碼中比較難理解的一段程式碼:

for itemset1 in L_last:
            for itemset2 in L_last:
                flag = 0
                for i in range(k-2):
                    if itemset1[i] != itemset2[i]:    #如果前k-2項中有一項不相等,則合併的項集必定不為頻繁項集
                        flag = 1
                        break
                if flag == 1:continue
                if itemset1[k-2] < itemset2[k-2]:
                    c = itemset1 + [itemset2[k-2]]    #合成待定k項集
                else:
                    continue

        對於項集L_last中包含k-1個項的任意兩個頻繁項集itemset1,itemset2,要使其合成為包含k個項的待定頻繁項集,要完成這樣的事,即就是給itemset1新增itemset2中其不存在的項;但為什麼要在合成的時候保證itemset1和itemset2中的前k-2個項相同呢?

       我們這樣來思考,合成的待定頻繁項集Lk可以看做為Lk-1與L1連線而成,這裡連線的時候保證連線時的L1的項要大於Lk-1的項,因為如果不大於的話,可能會出現這樣的無序項集['I1','I2','I5','I3'],由於頻繁項集的子集也是頻繁項集,故而['I1','I2','I3']是頻繁項集,故而其與L1連線可生成['I1','I2','I3','I5'],它與['I1','I2','I5','I3']是等價的,故而我們不妨將項集的格式都設為有序的,其並不影響我們的頻繁項集的合成過程。新頻繁項集Lk是建立在Lk-1上的,直接用Lk-1與L1連線有時在我們看來有點大材小用,因為對於一些L1中的元素,其與Lk-1連線後形成的Lk的子集,有可能不在Lk-1中,L1的範圍有點大,為簡化期間,我們可以直接讓Lk-1中的項集與Lk-1中其他項集中該項集不具備的項連線,這樣就簡化了問題,在廣度上降低演算法的複雜度。

參考資料:

《大資料探勘》  卓金武 周英 卞月青著

《python資料分析與挖掘實戰》  張良均 王路 譚立雲 蘇劍林著

相關文章