【機器學習】【無監督學習】【算法01-代碼實現】Apiori算法-篩選頻繁集

本節將會對於Apriori算法的頻繁集篩選過程,進行代碼的展示

1 回顧

上節提到,對於Apriori算法來說,其核心價值是在關聯分析的兩個過程,即篩選頻繁項集、關聯規則獲取的過程中,使得過程更加簡便。

首先,回顧一下Apriori算法中使得計算過程變得簡便的原理(Apriori原理):

  • 如果某個項集是頻繁的,那麼它的所有子集也是頻繁的
  • 反之,如果一個項集是非頻繁集,那麼 它的所有超集也是非頻繁的

此原理的論證部分,已在上文的結尾證明過。因此,我們現在需要考慮的問題就是,如何從原始數據集和出發,按照自己定義的”頻繁“程度,對項集進行篩選,得到頻繁項集。


2 篩選候選集

2.1 理論部分講解

我們很容易能夠想到,如果需要篩選,我們必須具有一個篩選前的總體部分。即,我們應該先從初始的以記錄形式存儲的數據集,轉化爲一個一個的項集。

這裏解釋一下項集的意思:以購買商品爲例,用戶一次購物,購買的商品A,B,C,D。這條記錄會以{商品A,商品B,商品C,商品D}的形式存儲在數據庫中,這樣的一條購買記錄稱爲一條記錄(或者事務)。我們用單詞縮寫tran(transaction)來表示
而這條記錄的子集,例如{商品A},{商品B}等,我們稱爲一個項集

那麼首先,我們應該獲取所有的候選集。因爲我們沒有現成可用的數據庫,因此先自己定義一個數據集合

'''
@author:小湯
編寫內容:Apriori算法
'''
#函數loadDataset()
#用於返回需要尋找頻繁項集的數據集合
def loadDataset():
    return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]

第二步:對於這樣的一個數據集合,我們通過什麼樣的步驟獲取其所有的組合呢?(在計算的過程中如何使用Apriori原理使得過程更加簡便?)

記錄 內容
tran1 [1,3,4]
tran2 [2,3,5]
tran3 [1,2,3,5]
tran4 [2,5]

首先,對於tran1來說,我們能夠抽取出來的子集應該是{1},{3},{4},然後是這三個子集的組合{1,3},{1,4},{3,4},{1,3,4}.
如果按照這一暴力方式,我們需要對每條記錄都進行子集的獲取,並且在獲取完所有的子集後,假設以上數據集一共有n種組合方式,再對每一種組合方式(項集)進行支持度的計算。

這樣的工作量是很大的。因此我們需要在生成的過程中,使用Apriori原理,對中間結果進行剪枝處理。
因此,在處理中,就可以提出了一個層的概念,如果學過數據結構的同學,對於層的概念應該會比較好理解,即類似於樹的結構。對於這樣的一個數據集,應該有這樣的處理方式如下圖:

頻繁集獲取流程
在這個過程中,使用到的迭代的思想,先生成只有一項的項集,計算支持度,不符合要求的直接剔除(也因此剔除了它的所有父集)。循環往復,最後就可以生成符合要求的頻繁集L。

接下來看代碼實現部分:


2.2 主體代碼部分

首先,我們根據上面的流程圖,整個算法實現應該至少需要幾個部分的功能函數,因此,按照流程和功能來分析就可以比較順利的完成所有的代碼:

2.2.1 創建C1層

輸入:原始數據集
輸出:只有一個項的項集(C1集)
邏輯:讀取數據集中的每一條記錄,獲取其中一項,若沒有記錄過,就存起來,若記錄過,就不存。

'''
@author:小湯
編寫內容:Apriori算法
'''
#創建C1集合
def createC1(dataset):
    C1=[]
    for transaction in dataset:#獲取數據集中的一條記錄
        for item in transaction:#獲取記錄中的一個項集,這裏的項集都是1一個元素構成
            if  [item] not in C1: #書上寫的是if not [] in 這裏改成了更加符合字然語言習慣的寫法
                C1.append([item]) #將元素作爲一個項集加入C1列表
    C1.sort()   #排序
    return list(map(frozenset,C1))#冰凍集合,不可修改 map() 會根據提供的函數對指定序列做映射
                             #即對C1中的每個元素進行冰凍化
                             #frozenset的作用在於,這種集合可以放在另外一個集合當中。因爲set類型並不保證自己是不變的,滿足不了其超集的確定性
                            #這裏必須要 在map外添加list數據類型轉換,原因是在python3中map返回的不再是列表而是一個迭代器

重點注意python中frozenset的使用: frozenset的作用在於,這種集合可以放在另外一個集合當中。因爲set類型並不保證自己是不變的,滿足不了其超集的確定性

然後,就需要一個函數,在之後的過程中不斷的篩選滿足條件的項集列表,第一步中則是將C1層篩選爲C2層。

2.2.2 創建L1~LN層

輸入:數據集合D(注意要轉換爲集合類型),候選項集合Ck,最小支持度minSupport
輸出:滿足頻繁條件的項集

邏輯:對輸入的C1的每一個項集:
		  對數據庫的每條記錄:
		  檢索其是否出現在記錄中
					如果出現過,則對其出現次數+1(使用字典ssCnt存儲)
					如果沒有,則跳過
		  完成以上計數過程後,進行支持度判斷
		  對於ssCnt中的每一項集,均做支持度的判斷
		  	如果符合頻繁條件,將其本身加入retList,否則不記錄
		  	記錄每一個項集和其對應的支持度(後續關聯規則使用)
		  	ps:個人認爲沒必要把每一個都存,只存頻繁集的就可以。不知作者這裏是不是寫錯了(因爲關聯規則只會用到頻繁集涉及到的元素的支持度)			
'''
@author:小湯
編寫內容:Apriori算法
'''
#創建L1~LN集合,這一步計算進來的項集的支持度,返回滿足的。
def scanD(D,Ck,minSupport): #數據集合D(注意要轉換爲集合類型),候選項集合Ck,最小支持度minSupport
    ssCnt={}                #空字典 ssCnt,字典的鍵是項集,值是出現次數,之後要換成支持度
    for tid in D:           #遍歷數據集合 tid表示數據集中的一條記錄
        for can in Ck:      #對於候選集和Ck中的一個項集can來說,第一遍就是C1是Ck候選集,can是C1中的所有項集
            if can.issubset(tid):  #如果can是tid的一個子集(如果項集出現在記錄中)
                if can not in ssCnt: #not ssCnt.has_key(can) 因爲has_key用法在python3中被移除了,所以改成此用法
                    ssCnt[can]=1      #這裏就是一個計數的判斷
                else:
                    ssCnt[can]+=1
    numItems = float(len(D)) #獲取數據集合的記錄長度
    retList= []    #構建空列表用於存儲滿足最小支持度的項集
    supportData = {}
    for key in ssCnt:  #計算和判斷支持度,遍歷所有的key
        support=ssCnt[key]/numItems  #ssCnt[key]是對應的值,除數量轉化成支持度
        if support >=minSupport:
            retList.append(key)   #將頻繁集,插入首部,0代表在字頭插入
        supportData[key]=support    #將項集作爲key,支持度作爲value存入supportdata(每個項集都會被記錄)
    return retList,supportData

至此,我們可以使用以上代碼生成C1層和L1層的項集。

2.2.3 生成排列組合的功能函數

在生成L1後,我們應該使用一個排列組合函數使得L1中的函數能夠自由的組合成C2項集
輸入:LN函數,k(表示生產的項集有多少個元素)
輸出:C (N+1)函數
在這一個函數中,我們需要特別注意有一個可以簡化的地方,具體可以研究以下實例:

利用{0,1}、 {0,2}、 {1,2}來創建三元素 項集,應該怎麼做?
如果將每兩個集合合併,就會得到{0, 1, 2}、 {0, 1, 2}、 {0, 1, 2}。也就是說,
同樣的結果集合會重複3次。接下來需要掃描三元素項集列表來得到非重複結果,我們要做的是 確保遍歷列表的次數最少
如果比較集合{0,1}、 {0,2}、 {1,2}的第1個元素並只對第1個元素相同的集合求並操作,則只需要操作一次,無需去重

這是因爲,在我們處理的過程中,每次需要進行組合的集合的元素個數都相同(且元素按照順序排列),因此,如果比較除最後一位元素的剩餘部分,若相同(只有最後一個元素不同),則可以進行一次合併。(如果無法理解,也可以遍歷取並集,再去重)

'''
@author:小湯
編寫內容:Apriori算法
'''
#這個函數中的k-2是爲了能夠減少重複次數
#因爲如果只是對所有的備選元素進行取並集的操作,還需要加入一個去重的操作
#如果只將最後一個元素不同的兩個集合進行合併,就不需要加入去重操作
def aprioriGen(Lk, k): #creates Ck,k表示生產的項集有多少個元素
    retList = []        #空列表存儲Ck
    lenLk = len(Lk)
    for i in range(lenLk):#生成候選集
        for j in range(i+1, lenLk):
            L1 = list(Lk[i])[:k-2]   #list(Lk[i]) 獲取了一個項集並轉化爲list
            L2 = list(Lk[j])[:k-2]   #這裏的L1和L2是前後兩個項集的第1~K-1項,因爲list計數從0開始,所以獲取K-2項
            L1.sort()
            L2.sort()
            if L1==L2:              #如果相同,即兩個集合中,有最後一個元素的不同,就可以合併成下一級項集
                retList.append(Lk[i] | Lk[j]) #取並集
    return retList

2.3 主函數

功能函數全部撰寫完畢,則可以加入驅動所有函數的主函數。按照流程圖來寫如下:
邏輯:創建C1—>創建L1—>排列組合—>創建C2—>…—>創建LN

#主函數
def apriori(dataSet, minSupport=0.5):
    C1 = createC1(dataSet)    #創建C1列表
    D = list(map(set, dataSet))  #將數據集合變更爲集合類型
    L1, supportData = scanD(D, C1, minSupport) #創建L1列表
    L = [L1]                                   #將L1放入一個列表L中,L1現在是第0層
    k = 2                                       #L後續會包括L2~Ln
    while (len(L[k-2]) > 0):                    #第一次運行while時:對L中的第0層也就是L1進行處理,後續以此類推
        Ck = aprioriGen(L[k-2], k)              #首先,L中存L1,L[2-2]即L[0]就是L1,計算L1的下一層組合,即返回的Ck
        Lk, supK = scanD(D, Ck, minSupport)     #再對組合Ck的項集進行支持度的計算,得到L某層,和支持度
        supportData.update(supK)                #對於supportData進行更新,即項集和它的支持度鍵值對
        L.append(Lk)                            #將第2層~第N層的滿足項集加入L列表
        k += 1                                  #進入下一層
    return L, supportData

3 結果

對以上結果進行運行:

data=loadDataset()
L,s=apriori(data,0.5)  #獲取頻繁集

輸出:

頻繁集:[[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})], [frozenset({1, 3}), frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5})], [frozenset({2, 3, 5})], []]

項集和其對應的支持度:{frozenset({1}): 0.5, frozenset({3}): 0.75, frozenset({4}):0.25 , frozenset({2}): 0.75, frozenset({5}): 0.75, frozenset({1, 3}): 0.5, frozenset({2, 3}): 0.5, frozenset({3, 5}): 0.5, frozenset({2, 5}): 0.75,frozenset({1, 2}): 0.25, frozenset({1, 5}): 0.25, rozenset({2, 3, 5}): 0.5}

注意:以上就出現了一些不是頻繁集合的項集和其對應的支持度,個人在代碼部分已經提出了認爲沒必要存儲。測試後續關聯規則代碼時也並不影響計算。因此用刪除線畫出。不過大家確實可以看到,程序可以有效地把低於0.5支持度的項集篩選掉。

下一節:【機器學習】【無監督學習】【算法01-理論2】Apiori算法-關聯規則獲取
*以上代碼來自於參考《機器學習實戰》,本人手動撰寫並註釋。若有侵權,請聯繫刪除。

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