通俗易懂Apriori算法及Python實現


 

本篇分為三個部分:

 

一、算法背景

  啤酒與尿布故事:

  某超市為增加銷售量,提取出了他們超市所有的銷售記錄進行分析。在對這些小票數據進行分析時,發現男性顧客在購買嬰兒尿片時,通常會順便搭配帶打啤酒來犒勞自己,於是超市就想如果把這兩種平時看不出有關聯的商品擺在一起,是不是能方便顧客同時提升商品的銷量。於是嘗試將啤酒和尿布擺在一起的上櫃策略,最后果然兩樣商品的銷量雙雙提升。

   

  聰明的現代店家(甩餅)故事:

   甩餅是2020年的一個店家,他聽了啤酒與尿布故事后在想能不能有種便捷的方法可以找出這些有關聯商品。比如說,客人完成購買行為后,客人會有小票。店家會有相應的購買記錄(客人一次購買行為,購買了什么物品)。那么甩餅就可以利用這些購買記錄來進行分析!舉個栗子

  比如客人,小敏去甩餅超市買泡面,小敏飯量大,一桶面不夠吃,兩桶又吃不了,她就在想那就加個蛋吧?還不夠!那再來根香腸!

  其他很多客人都會有小敏這樣的購物習慣

  所以,泡面、鹵蛋、香腸就極大可能成組合,出現在每張小票上。

  通過看小票有哪些商品經常成組合出現,則它們極大可能是關聯商品。

 

  支持度:

  甩餅要怎么通過這些小票記錄找出這些關聯商品呢?於是他學習了Apriori算法。Apriori算法認為,如果某一組商品組合在所有小票中出現的次數太少,那么我們就認這組的商品之間沒有什么關聯(例如:A是頭孢、B是白酒,那么A和B基本上不可能同時出現在同一張小票上,那么可以推斷出他們沒有什么關聯)

  那么甩餅又在想,要這組商品組合要出現在少於多少張小票,我們就可以認為它里面的商品是無關聯的呢?

  這個次數可以任意規定,我們可以把小票的張數稱為支持度。這個支持度不宜太大也不宜太小,如果這個支持度太大,那么很多商品本來有關聯(比如方便面和火腿),結果因為支持度定太大,就只能認為他們無關聯了(比如說有一百張小票,你非得要求一百張小票都要有方便面和火腿,才認為它們有關聯)。那如果定的支持度太小,那么很多無關聯的商品(比如頭孢和酒)又太容易被看錯成有關聯(比如說有一百張小票,你只要求有一張小票同時出現頭孢和酒,你就認為它們有關聯)

  所以這個值取值一定得合適,這個合適的支持度取值我們就稱之為最小支持度(商品組合出現的小票張數多於這個值,這組商品就有關。如果低於這個值,這組商品就無關)

   那么甩餅有了購買記錄(客人購買的小票),選取好一個最小支持度。對於特定的商品組合,他只需看這組商品組合在小票同時出現的支持度(次數)是不是大於這個最小支持度就可以判斷,這組商品是否有關聯(有這組商品組合的小票張數是不是大於最小支持度)

  舉個栗子:

  商店里有5種商品,今天開出了4張小票,每行對應一張小票(為了方便起見,把ABCDE換成數字 1 2 3 4 5):

  [A C D] --> 1 3 4
  [B C E] --> 2 3 5
  [A B C E] --> 1 2 3 5
  [B E] --> 2 5

    

  這時A在三張小票中出現,那么A的支持度support(A)=3

  AC商品組合在同樣三張小票中出現,那么AC組合的支持度support(AC)=3

  如果甩餅選擇最小支持度min_support=2,那么即可認為商品組合支持度大於最小支持度的組內商品是有關聯的

  所以support(AC)>min_support  則A、C可認為有關聯

 

二、算法介紹

  先介紹兩個定律:

  Apriori定律1 :如果某商品組合小於最小支持度,則就將它舍去,它的超集必然不是頻繁項集。
  Apriori定律2 :如果一個集合是頻繁項集,即這個商品組合支持度大於最小支持度,則它的所有子集都是頻繁項集

  

  為了方便對兩個定律的理解,還是用之前的小票數據,分別舉例說明:

  [A C D] --> 1 3 4
  [B C E] --> 2 3 5
  [A B C E] --> 1 2 3 5
  [B E] --> 2 5

  

  1°  對於Apriori定律1,對於BC這個商品組合,它出現的次數為1,即support(BC)=1<min_support=2,那么對於他的超集如BCE,BCE出現的次數必不可能比BC出現的次數多,即support(BCE)<=support(BC)<min_support 所以BCE必不可能是頻繁項集     (補充:當商品組合的支持度大於最小支持度,則認為該商品組合為頻繁項集)

  2° 對於Apriori定律2,基於Apriori定律1的很容易理解Apriori定律2。若AC的支持度大於2,那么A的支持度必大於2(AC都出現3次了,A當然也至少出現3次)

  

  算法實現思路:

  1、從一個商品開始找,找出所有的頻繁項集(就是該商品的支持度大於最小支持度)

  2、根據頻繁項集確認下一組候選集

  3、從候選集篩選頻繁項集,從而遞歸步驟2、步驟3 ,直到不能遞歸為止

 

  過程如圖所示:

     

 

 

   

  (1)第一次掃描

  首先,求第一次掃描數據庫后的候選集。
  從圖中可以看出,第一次掃描后,可以求出單個商品的支持度(圖中支持度用出現次數表示),這個表稱為第一次候選集,即下圖所示:
     在這里插入圖片描述

  在第一次候選集基礎上,求出第一次頻繁項集,頻繁項就是該商品的支持度 大於 最小支持度,支持度選擇時隨意的,在這里取最小支持度為 min_support=2
  那么第一次頻繁項集就是第一次候選集中,支持度大於或大於2的所有商品集合。即下表,(把 D 商品從表中去除了,因為它的支持度小於2)
        在這里插入圖片描述

  (2)第二次掃描
  先求出第二次的候選集。
  即在第一次頻繁項集的基礎上,找出第二次候選集,對商品進行組合,形成一個2元組,4種商品,不同組合有C42種,即 4x3=12 種,形成的表稱為第二次候選集表。如下圖
        在這里插入圖片描述
  在求第二次頻繁項集
  對於上表,求出這兩種商品同時出現在總記錄中的次數(即求支持度),然后去掉支持度小於2的商品組合,形成的表即為第二次頻繁項集。如下表
在這里插入圖片描述

  (3)第三次掃描
  先求出第三次的候選集。
  即在第二次頻繁項集的基礎上,找出第三次候選集。
  就是將原來的2元組,拓展為3元組,怎么拓展呢?

  設K為第K次掃描,要求第K個候選集,找出上一次掃描的頻繁項集,然后觀察里面的記錄,對於里面的每個記錄,前(K-2)個前綴相同的,歸為一類,在同一類別中進行合並。

  比如 這第三次掃描,要求出它的候選集,先找出上次掃描形成的第二次頻繁項集表,里面有4條記錄,分別為,AC,BC,BE,CE,這些記錄中,前(K-2)個前綴,就是前(3-2)個前綴,也就是第一個前綴相同的歸為一類,接着在屬於同一類的記錄中,進行合並,比如BC,BE,它門的第一個前綴都是B,那么在這一類中,把它門合並起來就形成了BCE。還剩下AC、CE,它門第一個前綴不相同,也沒有其他元素和它門相同,那么就不用去管了。如果你非要合並,把AC、CE合並為ACE,我們看一下ACE的子集,它的子集是{AC、CE、AE},可以看出AC、CE確實是頻繁項集,但是AE呢,你在求第二次的候選集時,因為AE的支持度小於2,你把它去除了,那么ACE也必然不是頻繁項集。(Apriori定律1 :如果某商品組合小於最小支持度,則就將它舍去,它的超集必然不是頻繁項集。)
  

  減枝的概念:
  比如剛才新形成的BCE這個組合,它的子集是{BC、CE、BE},顯然BC和CE本來就是一個頻繁項集,但是CE呢,我們必須對比上一次頻繁項集中的元素,也就是第2次頻繁項集的元素,如果CE不是第二次頻繁項集的元素,那么就把新形成的 BC E 這個元素給 “減去”,也就是減枝,這一點我在代碼中有體現,具體請看后面的代碼。
  (4)第四次掃描和前兩次原理,一樣,留給讀者做練習。
  最后值得一提的是,當最后生成的候選集表中,只有0個或1個的話,循環就結束了。

 

 

三、python代碼實現

   

  代碼輸出如下:

 

 

   

  代碼如下:

   

'''
#請從最后的main方法開始看起
Apriori算法,頻繁項集算法
A 1,   B 2,   C 3,   D 4,   E 5
1 [A C D]       1 3 4
2 [B C E]       2 3 5
3 [A B C E]     1 2 3 5
4 [B E]         2 5

min_support = 2  或  = 2/4
'''

def item(dataset):      #求第一次掃描數據庫后的 候選集,(它沒法加入循環)
    c1 = []     #存放候選集元素

    for x in dataset:       #就是求這個數據庫中出現了幾個元素,然后返回
        for y in x:
            if [y] not in c1:
                c1.append( [y] )
    c1.sort()
    #print(c1)
    return c1

def get_frequent_item(dataset, c, min_support):
    cut_branch = {}     #用來存放所有項集的支持度的字典
    for x in c:
        for y in dataset:
            if set(x).issubset(set(y)):     #如果 x 在 y中,就把對應元素后面加 1
                cut_branch[tuple(x)] = cut_branch.get(tuple(x), 0) + 1     #cut_branch[y] = new_cand.get(y, 0)表示如果字典里面沒有想要的關鍵詞,就返回0
    #print(cut_branch)

    Fk = []       #支持度大於最小支持度的項集,  即頻繁項集
    sup_dataK = {}  #用來存放所有 頻繁 項集的支持度的字典

    for i in cut_branch:
        if cut_branch[i] >= min_support:    #Apriori定律1  小於支持度,則就將它舍去,它的超集必然不是頻繁項集
            Fk.append( list(i))
            sup_dataK[i] = cut_branch[i]
    #print(Fk)
    return Fk, sup_dataK

def get_candidate(Fk, K):       #求第k次候選集
    ck = []    #存放產生候選集

    for i in range(len(Fk)):
        for j in range(i+1, len(Fk)):
            L1 = list(Fk[i])[:K-2]
            L2 = list(Fk[j])[:K-2]
            L1.sort()
            L2.sort() #先排序,在進行組合

            if L1 == L2:
                if K > 2:       #第二次求候選集,不需要進行減枝,因為第一次候選集都是單元素,且已經減枝了,組合為雙元素肯定不會出現不滿足支持度的元素
                    new = list(set(Fk[i]) ^ set(Fk[j]) ) #集合運算 對稱差集 ^ (含義,集合的元素在t或s中,但不會同時出現在二者中)
                    #new表示,這兩個記錄中,不同的元素集合
                    # 為什么要用new? 比如 1,2     1,3  兩個合並成 1,2,3   我們知道1,2 和 1,3 一定是頻繁項集,但 2,3呢,我們要判斷2,3是否為頻繁項集
                    #Apriori定律1 如果一個集合不是頻繁項集,則它的所有超集都不是頻繁項集
                else:
                    new = set()
                for x in Fk:
                    if set(new).issubset(set(x)) and list(set(Fk[i]) | set(Fk[j])) not in ck:  #減枝 new是 x 的子集,並且 還沒有加入 ck 中
                        ck.append( list(set(Fk[i]) | set(Fk[j])) )
    #print(ck)
    return ck

def Apriori(dataset, min_support = 2):
    c1 = item (dataset) #返回一個二維列表,里面的每一個一維列表,都是第一次候選集的元素
    f1, sup_1 = get_frequent_item(dataset, c1, min_support)       #求第一次候選集

    F = [f1]      #將第一次候選集產生的頻繁項集放入 F ,以后每次掃描產生的所有頻繁項集都放入里面
    sup_data = sup_1       #一個字典,里面存放所有產生的候選集,及其支持度

    K = 2 #從第二個開始循環求解,先求候選集,在求頻繁項集

    while (len(F[K-2]) > 1):  #k-2是因為F是從0開始數的     #前一個的頻繁項集個數在2個或2個以上,才繼續循環,否則退出
        ck = get_candidate(F[K-2], K)  #求第k次候選集
        fk, sup_k = get_frequent_item(dataset, ck, min_support)     #求第k次頻繁項集

        F.append(fk)    #把新產生的候選集假如F
        sup_data.update(sup_k)  #字典更新,加入新得出的數據
        K+=1
    return F, sup_data    #返回所有頻繁項集, 以及存放頻繁項集支持度的字典

if __name__ == '__main__':
    dataset = [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]       #裝入數據 二維列表
    F, sup_data = Apriori(dataset, min_support = 2)   #最小支持度設置為2

    print("具有關聯的商品是{}".format(F))   #帶變量的字符串輸出,必須為字典符號表示
    print('------------------')
    print("對應的支持度為{}".format(sup_data))
    

 

 

 

  

 

 

 

 

 

 

  

 

  博文內容改自:https://blog.csdn.net/qq_39872846/article/details/105291265

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM