前言
想必大家都聽過資料探勘領域那個經典的故事 - "啤酒與尿布" 的故事。
那麼,具體是怎麼從海量銷售資訊中挖掘出啤酒和尿布之間的關係呢?
這就是關聯分析所要完成的任務了。
本文將講解關聯分析領域中最為經典的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:但它不能用來發現關聯規則)