第十四篇:Apriori 關聯分析演算法原理分析與程式碼實現

穆晨發表於2017-01-19

前言

       想必大家都聽過資料探勘領域那個經典的故事 - "啤酒與尿布" 的故事。

       那麼,具體是怎麼從海量銷售資訊中挖掘出啤酒和尿布之間的關係呢?

       這就是關聯分析所要完成的任務了。

       本文將講解關聯分析領域中最為經典的Apriori演算法,並給出具體的程式碼實現。

關聯分析領域的一些概念

       1. 頻繁項集: 資料集中經常出現在一起的物品的集合。例如 "啤酒和尿布"

       2. 關聯規則: 指兩個物品集之間可能存在很強的關係。例如 "{啤酒} -> {尿布}" 就是一條關聯規則。

       3. 支援度: 資料集中,出現了某個物品集的資料項佔總資料項的比重(某些地方也解釋為次數)。

       4. 可信度: 這個概念是針對某條關聯規則而定的。它是指兩個物品集的支援度和其中某個物品集的支援度之比,如 "支援度{啤酒,尿布} / 支援度{尿布}"。

       因此,用這些屬於來解釋啤酒與尿布的故事,那就是:{啤酒,尿布}是一個頻繁項集;"{啤酒} -> {尿布}" 就是一條關聯規則;顧客買尿布的同時買啤酒的可能性為 "支援度{啤酒,尿布} / 支援度{尿布}"。

       那麼對海量的資料,假如要得到支援度大於0.8的所有頻繁項集,該怎麼做?

       如果用蠻力法一個個統計,是根本不現實的,那樣計算量實在太大。

       本文將要介紹的Apriori關聯分析演算法意義就在於能夠大幅度減少這種情況的計算量,並從頻繁項集中高效檢索出關聯規則,從而大大減少關聯規則學習所需要消耗的計算量

Apriori演算法基本原理

  如果{0,1}是頻繁項集,那麼{0}和{1}也都是頻繁項集。

  這顯然是正確的命題。

  其逆否命題 - ”如果{0}和{1}不都是頻繁項集,那麼{0,1}不是頻繁項集" 自然也是正確的。-> 這就是 Apriori 演算法的核心思想之一。

  這樣,一旦發現某個集合不是頻繁項集,那麼它的所有超集也都不是頻繁項集,就不用浪費功夫去對它們進行檢索了。

  檢索出頻繁項集之後,接下來檢索出所有合乎要求的關聯規則。

  如果某條規則不滿足最小可信度要求,那麼該規則的所有子集也不會滿足。 -> 這是 Apriori 演算法的核心思想的另一部分。

  PS:這裡務必思考清楚規則是怎麼劃分的,什麼叫某個規則的子集。

  這樣,和上一步同理,也能高效的從頻繁項集中檢索出關聯規則了。

  具體實現將分為頻繁集檢索關聯規則學習兩個部分進行講解。

頻繁項集檢索實現思路與實現程式碼

  一種經典的實現方法是 "分級法":

  演算法框架是在兩個大的步驟 - 篩選/過濾之間不斷迭代,直到所有情況分析完畢。

  每次篩選的結果都要指定元素個數的情況,因此所謂分級,也就是依次討論指定元素個數情況的項集。

  在篩選之後,就是過濾。

  過濾有兩層意義,一個是項集必須在資料集中存在。這是第一層過濾;還有一層過濾,是指支援度過濾。只有滿足支援度要求的項集才能儲存下來。

  過濾之後,基於過濾集再進行篩選,每次篩選的元素個數都比上一次篩選的元素個數多一個。

  然後繼續過濾。如此反覆,直到最後一次篩選過濾完成。

  虛擬碼實現:

1 當集合中項的個數大於0時:
2     構建一個 k 個項組成的候選項集的列表
3     檢查資料以確認每個項集都是頻繁的
4     保留頻繁項集並構建 k+1 項組成的候選項集列表

       其中檢查每個項集是否頻繁部分的虛擬碼:

1 對資料集中的每條交易記錄:
2     對每個候選集元素:
3         檢查是否為資料集元素,是才保留。
4 對每個資料集
5     如果支援度達到要求才保留
6 返回過濾後的頻繁項集 - 也即過濾集

       Python實現及測試程式碼:

  1 def loadDataSet():
  2     '返回測試資料'
  3     
  4     return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
  5 
  6 #===================================
  7 # 輸入:
  8 #        dataSet: 資料集
  9 # 輸出:
 10 #        map(frozenset, C1): 候選集
 11 #===================================
 12 def createC1(dataSet):
 13     '建立候選集'
 14     
 15     C1 = []
 16     for transaction in dataSet:
 17         for item in transaction:
 18             if not [item] in C1:
 19                 C1.append([item])
 20                 
 21     C1.sort()
 22     
 23     # 返回的集合元素都是frozenset型別 -> 因為以後要用來做鍵。
 24     return map(frozenset, C1)
 25 
 26 #=============================================
 27 # 輸入:
 28 #        D: 資料集 (set格式)
 29 #        Ck: 候選集
 30 #        minSupport: 最小支援度
 31 # 輸出:
 32 #        retList: 過濾集
 33 #        supportData: 支援集(挖掘關聯規則時使用)
 34 #=============================================
 35 def scanD(D, Ck, minSupport):
 36     '由候選集得到過濾集'
 37     
 38     # 統計候選元素出現的次數
 39     ssCnt = {}
 40     for tid in D:
 41         for can in Ck:
 42             if can.issubset(tid):
 43                 if not ssCnt.has_key(can): ssCnt[can]=1
 44                 else: ssCnt[can] += 1
 45                 
 46     # 構建過濾集和支援集
 47     numItems = float(len(D))
 48     retList = []
 49     supportData = {}
 50     for key in ssCnt:
 51         support = ssCnt[key]/numItems
 52         if support >= minSupport:
 53             retList.insert(0,key)
 54         supportData[key] = support
 55         
 56     return retList, supportData
 57 
 58 #===================================
 59 # 輸入:
 60 #        Lk: 過濾集
 61 #        k: 當前項集元素個數
 62 # 輸出:
 63 #        retList: 候選集
 64 #===================================
 65 def aprioriGen(Lk, k):
 66     '由過濾集得到候選集'
 67     
 68     # 重組過濾集,得到新的候選集。
 69     retList = []
 70     lenLk = len(Lk)
 71     for i in range(lenLk):
 72         for j in range(i+1, lenLk): 
 73             # 留意下重組技巧
 74             L1 = list(Lk[i])[:k-2]; L2 = list(Lk[j])[:k-2]
 75             L1.sort(); 
 76             L2.sort()
 77             if L1==L2:
 78                 retList.append(Lk[i] | Lk[j])
 79                 
 80     return retList
 81 
 82 #=============================================
 83 # 輸入:
 84 #        dataSet: 資料集
 85 #        minSupport: 最小支援度
 86 # 輸出:
 87 #        L: 頻繁集
 88 #        supportData: 支援集(挖掘關聯規則時使用)
 89 #=============================================
 90 def apriori(dataSet, minSupport = 0.5):
 91     '求頻繁項集及其對應支援度'
 92     
 93     C1 = createC1(dataSet)
 94     D = map(set, dataSet)
 95     L1, supportData = scanD(D, C1, minSupport)
 96     L = [L1]
 97     k = 2
 98     while (len(L[k-2]) > 0):
 99         Ck = aprioriGen(L[k-2], k)
100         Lk, supK = scanD(D, Ck, minSupport)
101         supportData.update(supK)
102         L.append(Lk)
103         k += 1
104         
105     return L, supportData
106     
107 def main():
108     'Apriori頻繁集檢索'
109     
110     L, s = apriori (loadDataSet())
111     
112     print L
113     print s

       測試結果:

      

關聯規則學習實現思路與實現程式碼

       上一部分的工作是從資料集中檢索出頻繁集;而這一部分是根據頻繁集學習關聯規則。

       上一部分的工作是通過篩選集中的元素個數進行分級;而這一部分是針對規則右部的元素個數進行分級。

       另外還要注意,只用檢索單個頻繁集之內的關聯規則就可以了。

       實現程式碼:

 1 #===================================
 2 # 輸入:
 3 #        L: 頻繁集
 4 #        supportData: 支援集
 5 #        minConf: 最小可信度
 6 # 輸出:
 7 #        bigRuleList: 規則集
 8 #===================================
 9 def generateRules(L, supportData, minConf=0.7):
10     '從某個頻繁集中學習關聯規則'
11     
12     bigRuleList = []
13     for i in range(1, len(L)):
14         for freqSet in L[i]:
15             H1 = [frozenset([item]) for item in freqSet]
16             if (i > 1):
17                 rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
18             else:
19                 calcConf(freqSet, H1, supportData, bigRuleList, minConf)
20     return bigRuleList         
21 
22 #===================================
23 # 輸入:
24 #        L: 頻繁集
25 #        supportData: 支援集
26 #        minConf: 最小可信度
27 # 輸出:
28 #        bigRuleList: 規則集
29 #===================================
30 def calcConf(freqSet, H, supportData, brl, minConf=0.7):
31     '可信度過濾'
32     
33     prunedH = []
34     for conseq in H:
35         conf = supportData[freqSet]/supportData[freqSet-conseq]
36         if conf >= minConf: 
37             brl.append((freqSet-conseq, conseq, conf))
38             prunedH.append(conseq)
39             
40     return prunedH
41 
42 def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7):
43     '從某個頻繁項集中學習關聯規則'
44     
45     # 本次學習的規則右部中的元素個數
46     m = len(H[0])
47     if (len(freqSet) > (m + 1)):
48         # 重組規則右部
49         Hmp1 = aprioriGen(H, m+1)
50         # 規則學習
51         Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf)
52         if (len(Hmp1) > 1):
53             # 遞迴學習函式
54             rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)
55               
56 def main():
57     '關聯規則學習'
58     
59     L, s = apriori (loadDataSet())
60     
61     rules = generateRules(L, s)
62     print rules

       測試結果:

  

       測試資料為: [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]。

       結果的意思也就是說:1->3,5->2,2->5 的概率為 1。

       顯然這是和預計相吻合的。

小結

       1. Apriori關聯演算法在網路購物網站中用的非常多,可以基於此演算法搭建商品推薦系統。

       2. 但Apriori演算法也有一個缺點,那就是頻繁集的檢索速度還是不夠快,因為每級都要重新檢索一遍候選集(雖然候選集越來越小)。

       3. 針對 2 中的問題,下篇文章將介紹一個更為強大的發現頻繁集的演算法 - FP-growth。(PS:但它不能用來發現關聯規則)

相關文章