關聯分析
關聯分析是一種在大規模數據集中尋找有趣關系的任務。 這些關系可以有兩種形式:
- 頻繁項集(frequent item sets): 經常出現在一塊的物品的集合。
- 關聯規則(associational rules): 暗示兩種物品之間可能存在很強的關系。
相關術語
-
關聯分析(關聯規則學習): 從大規模數據集中尋找物品間的隱含關系被稱作
關聯分析(associati analysis)
或者關聯規則學習(association rule learning)
。 下面是用一個雜貨店
例子來說明這兩個概念,如下圖所示: -
頻繁項集: {葡萄酒, 尿布, 豆奶} 就是一個頻繁項集的例子。
-
關聯規則: 尿布 -> 葡萄酒 就是一個關聯規則。這意味着如果顧客買了尿布,那么他很可能會買葡萄酒。
那么 頻繁
的定義是什么呢?怎么樣才算頻繁呢? 度量它們的方法有很多種,這里我們來簡單的介紹下支持度和可信度。
- 支持度: 數據集中包含該項集的記錄所占的比例。例如上圖中,{豆奶} 的支持度為 4/5。{豆奶, 尿布} 的支持度為 3/5。
- 可信度: 針對一條諸如 {尿布} -> {葡萄酒} 這樣具體的關聯規則來定義的。這條規則的
可信度
被定義為支持度({尿布, 葡萄酒})/支持度({尿布})
,從圖中可以看出 支持度({尿布, 葡萄酒}) = 3/5,支持度({尿布}) = 4/5,所以 {尿布} -> {葡萄酒} 的可信度 = 3/5 / 4/5 = 3/4 = 0.75。
支持度
和 可信度
是用來量化 關聯分析
是否成功的一個方法。 假設想找到支持度大於 0.8 的所有項集,應該如何去做呢? 一個辦法是生成一個物品所有可能組合的清單,然后對每一種組合統計它出現的頻繁程度,但是當物品成千上萬時,上述做法就非常非常慢了。 我們需要詳細分析下這種情況並討論下 Apriori 原理,該原理會減少關聯規則學習時所需的計算量。
Apriori 原理
假設我們一共有 4 個商品: 商品0, 商品1, 商品2, 商品3。 所有可能的情況如下:
如果我們計算所有組合的支持度,也需要計算 15 次。即 2^N - 1 = 2^4 - 1 = 15。
隨着物品的增加,計算的次數呈指數的形式增長 ...
為了降低計算次數和時間,研究人員發現了一種所謂的 Apriori 原理,即某個項集是頻繁的,那么它的所有子集也是頻繁的。 例如,如果 {0, 1} 是頻繁的,那么 {0}, {1} 也是頻繁的。 該原理直觀上沒有什么幫助,但是如果反過來看就有用了,也就是說如果一個項集是 非頻繁項集
,那么它的所有超集也是非頻繁項集,如下圖所示:
在圖中我們可以看到,已知灰色部分 {2,3} 是 非頻繁項集
,那么利用上面的知識,我們就可以知道 {0,2,3} {1,2,3} {0,1,2,3} 都是 非頻繁的
。 也就是說,計算出 {2,3} 的支持度,知道它是 非頻繁
的之后,就不需要再計算 {0,2,3} {1,2,3} {0,1,2,3} 的支持度,因為我們知道這些集合不會滿足我們的要求。 使用該原理就可以避免項集數目的指數增長,從而在合理的時間內計算出頻繁項集。
Apriori 算法優缺點
* 優點:易編碼實現
* 缺點:在大數據集上可能較慢
* 適用數據類型:數值型 或者 標稱型數據。
Apriori 算法流程步驟:
* 收集數據:使用任意方法。
* 准備數據:任何數據類型都可以,因為我們只保存集合。
* 分析數據:使用任意方法。
* 訓練數據:使用Apiori算法來找到頻繁項集。
* 測試算法:不需要測試過程。
* 使用算法:用於發現頻繁項集以及物品之間的關聯規則。
Apriori 算法的使用
前面提到,關聯分析的目標包括兩項: 發現 頻繁項集
和發現 關聯規則
。 首先需要找到 頻繁項集
,然后才能發現 關聯規則
。Apriori
算法是發現 頻繁項集
的一種方法。 Apriori 算法的兩個輸入參數分別是最小支持度和數據集。 該算法首先會生成所有單個物品的項集列表。 接着掃描交易記錄來查看哪些項集滿足最小支持度要求,那些不滿足最小支持度要求的集合會被去掉。 燃盡后對生下來的集合進行組合以聲場包含兩個元素的項集。 接下來再重新掃描交易記錄,去掉不滿足最小支持度的項集。 該過程重復進行直到所有項集被去掉。
生成候選項集
下面會創建一個用於構建初始集合的函數,也會創建一個通過掃描數據集以尋找交易記錄子集的函數, 數據掃描的偽代碼如下:
- 對數據集中的每條交易記錄 tran
- 對每個候選項集 can
- 檢查一下 can 是否是 tran 的子集: 如果是則增加 can 的計數值
- 對每個候選項集
- 如果其支持度不低於最小值,則保留該項集
- 返回所有頻繁項集列表 以下是一些輔助函數。
加載數據集
# 加載數據集 def loadDataSet(): return [[1, 3, 4], [2, 3, 5], [1, 2, 3, 5], [2, 5]]
創建集合 C1。即對 dataSet 進行去重,排序,放入 list 中,然后轉換所有的元素為 frozenset
# 創建集合 C1。即對 dataSet 進行去重,排序,放入 list 中,然后轉換所有的元素為 frozenset def createC1(dataSet): """createC1(創建集合 C1) Args: dataSet 原始數據集 Returns: frozenset 返回一個 frozenset 格式的 list """ C1 = [] for transaction in dataSet: for item in transaction: if not [item] in C1: # 遍歷所有的元素,如果不在 C1 出現過,那么就 append C1.append([item]) # 對數組進行 `從小到大` 的排序 print 'sort 前=', C1 C1.sort() # frozenset 表示凍結的 set 集合,元素無改變;可以把它當字典的 key 來使用 print 'sort 后=', C1 print 'frozenset=', map(frozenset, C1) return map(frozenset, C1)
計算候選數據集 CK 在數據集 D 中的支持度,並返回支持度大於最小支持度(minSupport)的數據
# 計算候選數據集 CK 在數據集 D 中的支持度,並返回支持度大於最小支持度(minSupport)的數據 def scanD(D, Ck, minSupport): """scanD(計算候選數據集 CK 在數據集 D 中的支持度,並返回支持度大於最小支持度 minSupport 的數據) Args: D 數據集 Ck 候選項集列表 minSupport 最小支持度 Returns: retList 支持度大於 minSupport 的集合 supportData 候選項集支持度數據 """ # ssCnt 臨時存放選數據集 Ck 的頻率. 例如: a->10, b->5, c->8 ssCnt = {} for tid in D: for can in Ck: # s.issubset(t) 測試是否 s 中的每一個元素都在 t 中 if can.issubset(tid): if not ssCnt.has_key(can): ssCnt[can] = 1 else: ssCnt[can] += 1 numItems = float(len(D)) # 數據集 D 的數量 retList = [] supportData = {} for key in ssCnt: # 支持度 = 候選項(key)出現的次數 / 所有數據集的數量 support = ssCnt[key]/numItems if support >= minSupport: # 在 retList 的首位插入元素,只存儲支持度滿足頻繁項集的值 retList.insert(0, key) # 存儲所有的候選項(key)和對應的支持度(support) supportData[key] = support return retList, supportData
完整代碼地址: https://github.com/apachecn/AiLearning/blob/master/src/py2.x/ml/11.Apriori/apriori.py
組織完整的 Apriori 算法
輸入頻繁項集列表 Lk 與返回的元素個數 k,然后輸出所有可能的候選項集 Ck
# 輸入頻繁項集列表 Lk 與返回的元素個數 k,然后輸出所有可能的候選項集 Ck def aprioriGen(Lk, k): """aprioriGen(輸入頻繁項集列表 Lk 與返回的元素個數 k,然后輸出候選項集 Ck。 例如: 以 {0},{1},{2} 為輸入且 k = 2 則輸出 {0,1}, {0,2}, {1,2}. 以 {0,1},{0,2},{1,2} 為輸入且 k = 3 則輸出 {0,1,2} 僅需要計算一次,不需要將所有的結果計算出來,然后進行去重操作 這是一個更高效的算法) Args: Lk 頻繁項集列表 k 返回的項集元素個數(若元素的前 k-2 相同,就進行合並) Returns: retList 元素兩兩合並的數據集 """ retList = [] lenLk = len(Lk) for i in range(lenLk): for j in range(i+1, lenLk): L1 = list(Lk[i])[: k-2] L2 = list(Lk[j])[: k-2] # print '-----i=', i, k-2, Lk, Lk[i], list(Lk[i])[: k-2] # print '-----j=', j, k-2, Lk, Lk[j], list(Lk[j])[: k-2] L1.sort() L2.sort() # 第一次 L1,L2 為空,元素直接進行合並,返回元素兩兩合並的數據集 # if first k-2 elements are equal if L1 == L2: # set union # print 'union=', Lk[i] | Lk[j], Lk[i], Lk[j] retList.append(Lk[i] | Lk[j]) return retList
找出數據集 dataSet 中支持度 >= 最小支持度的候選項集以及它們的支持度。即我們的頻繁項集。
# 找出數據集 dataSet 中支持度 >= 最小支持度的候選項集以及它們的支持度。即我們的頻繁項集。 def apriori(dataSet, minSupport=0.5): """apriori(首先構建集合 C1,然后掃描數據集來判斷這些只有一個元素的項集是否滿足最小支持度的要求。那么滿足最小支持度要求的項集構成集合 L1。然后 L1 中的元素相互組合成 C2,C2 再進一步過濾變成 L2,然后以此類推,知道 CN 的長度為 0 時結束,即可找出所有頻繁項集的支持度。) Args: dataSet 原始數據集 minSupport 支持度的閾值 Returns: L 頻繁項集的全集 supportData 所有元素和支持度的全集 """ # C1 即對 dataSet 進行去重,排序,放入 list 中,然后轉換所有的元素為 frozenset C1 = createC1(dataSet) # 對每一行進行 set 轉換,然后存放到集合中 D = map(set, dataSet) print 'D=', D # 計算候選數據集 C1 在數據集 D 中的支持度,並返回支持度大於 minSupport 的數據 L1, supportData = scanD(D, C1, minSupport) # print "L1=", L1, "\n", "outcome: ", supportData # L 加了一層 list, L 一共 2 層 list L = [L1] k = 2 # 判斷 L 的第 k-2 項的數據長度是否 > 0。第一次執行時 L 為 [[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])]]。L[k-2]=L[0]=[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])],最后面 k += 1 while (len(L[k-2]) > 0): print 'k=', k, L, L[k-2] Ck = aprioriGen(L[k-2], k) # 例如: 以 {0},{1},{2} 為輸入且 k = 2 則輸出 {0,1}, {0,2}, {1,2}. 以 {0,1},{0,2},{1,2} 為輸入且 k = 3 則輸出 {0,1,2} print 'Ck', Ck Lk, supK = scanD(D, Ck, minSupport) # 計算候選數據集 CK 在數據集 D 中的支持度,並返回支持度大於 minSupport 的數據 # 保存所有候選項集的支持度,如果字典沒有,就追加元素,如果有,就更新元素 supportData.update(supK) if len(Lk) == 0: break # Lk 表示滿足頻繁子項的集合,L 元素在增加,例如: # l=[[set(1), set(2), set(3)]] # l=[[set(1), set(2), set(3)], [set(1, 2), set(2, 3)]] L.append(Lk) k += 1 # print 'k=', k, len(L[k-2]) return L, supportData
到這一步,我們就找出我們所需要的 頻繁項集
和他們的 支持度
了,接下來再找出關聯規則即可!
完整代碼地址: https://github.com/apachecn/AiLearning/blob/master/src/py2.x/ml/11.Apriori/apriori.py
從頻繁項集中挖掘關聯規則
前面我們介紹了用於發現 頻繁項集
的 Apriori 算法,現在要解決的問題是如何找出 關聯規則
。
要找到 關聯規則
,我們首先從一個 頻繁項集
開始。 我們知道集合中的元素是不重復的,但我們想知道基於這些元素能否獲得其它內容。 某個元素或某個元素集合可能會推導出另一個元素。 從先前 雜貨店
的例子可以得到,如果有一個頻繁項集 {豆奶,萵苣},那么就可能有一條關聯規則 “豆奶 -> 萵苣”。 這意味着如果有人買了豆奶,那么在統計上他會購買萵苣的概率比較大。 但是,這一條件反過來並不總是成立。 也就是說 “豆奶 -> 萵苣” 統計上顯著,那么 “萵苣 -> 豆奶” 也不一定成立。
前面我們給出了 頻繁項集
的量化定義,即它滿足最小支持度要求。
對於 關聯規則
,我們也有類似的量化方法,這種量化指標稱之為 可信度
。
一條規則 A -> B 的可信度定義為 support(A | B) / support(A)。(注意: 在 python 中 | 表示集合的並操作,而數學書集合並的符號是 U)。A | B
是指所有出現在集合 A 或者集合 B 中的元素。
由於我們先前已經計算出所有 頻繁項集
的支持度了,現在我們要做的只不過是提取這些數據做一次除法運算即可。
一個頻繁項集可以產生多少條關聯規則呢?
如下圖所示,給出的是項集 {0,1,2,3} 產生的所有關聯規則:
與我們前面的 頻繁項集
生成一樣,我們可以為每個頻繁項集產生許多關聯規則。
如果能減少規則的數目來確保問題的可解析,那么計算起來就會好很多。
通過觀察,我們可以知道,如果某條規則並不滿足 最小可信度
要求,那么該規則的所有子集也不會滿足 最小可信度
的要求。
如上圖所示,假設 123 -> 3
並不滿足最小可信度要求,那么就知道任何左部為 {0,1,2} 子集的規則也不會滿足 最小可信度
的要求。 即 12 -> 03
, 02 -> 13
, 01 -> 23
, 2 -> 013
, 1 -> 023
, 0 -> 123
都不滿足 最小可信度
要求。
可以利用關聯規則的上述性質屬性來減少需要測試的規則數目,跟先前 Apriori 算法的套路一樣。
以下是一些輔助函數:
計算可信度
# 計算可信度(confidence) def calcConf(freqSet, H , supportData, brl, minConf=0.7): """calcConf(對兩個元素的頻繁項,計算可信度,例如: {1,2}/{1} 或者 {1,2}/{2} 看是否滿足條件) Args: freqSet 頻繁項集中的元素,例如: frozenset([1, 3]) H 頻繁項集中的元素的集合,例如: [frozenset([1]), frozenset([3])] supportData 所有元素的支持度的字典 brl 關聯規則列表的空數組 minConf 最小可信度 Returns: prunedH 記錄 可信度大於閾值的集合 """ # 記錄可信度大於最小可信度(minConf)的集合 prunedH = [] for conseq in H: # 假設 freqSet = frozenset([1, 3]), H = [frozenset([1]), frozenset([3])],那么現在需要求出 frozenset([1]) -> frozenset([3]) 的可信度和 frozenset([3]) -> frozenset([1]) 的可信度 # print 'confData=', freqSet, H, conseq, freqSet-conseq conf = supportData[freqSet]/supportData[freqSet-conseq] # 支持度定義: a -> b = support(a | b) / support(a). 假設 freqSet = frozenset([1, 3]), conseq = [frozenset([1])],那么 frozenset([1]) 至 frozenset([3]) 的可信度為 = support(a | b) / support(a) = supportData[freqSet]/supportData[freqSet-conseq] = supportData[frozenset([1, 3])] / supportData[frozenset([1])] if conf >= minConf: # 只要買了 freqSet-conseq 集合,一定會買 conseq 集合(freqSet-conseq 集合和 conseq 集合是全集) print freqSet-conseq, '-->', conseq, 'conf:', conf brl.append((freqSet-conseq, conseq, conf)) prunedH.append(conseq) return prunedH
遞歸計算頻繁項集的規則
# 遞歸計算頻繁項集的規則 def rulesFromConseq(freqSet, H, supportData, brl, minConf=0.7): """rulesFromConseq Args: freqSet 頻繁項集中的元素,例如: frozenset([2, 3, 5]) H 頻繁項集中的元素的集合,例如: [frozenset([2]), frozenset([3]), frozenset([5])] supportData 所有元素的支持度的字典 brl 關聯規則列表的數組 minConf 最小可信度 """ # H[0] 是 freqSet 的元素組合的第一個元素,並且 H 中所有元素的長度都一樣,長度由 aprioriGen(H, m+1) 這里的 m + 1 來控制 # 該函數遞歸時,H[0] 的長度從 1 開始增長 1 2 3 ... # 假設 freqSet = frozenset([2, 3, 5]), H = [frozenset([2]), frozenset([3]), frozenset([5])] # 那么 m = len(H[0]) 的遞歸的值依次為 1 2 # 在 m = 2 時, 跳出該遞歸。假設再遞歸一次,那么 H[0] = frozenset([2, 3, 5]),freqSet = frozenset([2, 3, 5]) ,沒必要再計算 freqSet 與 H[0] 的關聯規則了。 m = len(H[0]) if (len(freqSet) > (m + 1)): print 'freqSet******************', len(freqSet), m + 1, freqSet, H, H[0] # 生成 m+1 個長度的所有可能的 H 中的組合,假設 H = [frozenset([2]), frozenset([3]), frozenset([5])] # 第一次遞歸調用時生成 [frozenset([2, 3]), frozenset([2, 5]), frozenset([3, 5])] # 第二次 。。。沒有第二次,遞歸條件判斷時已經退出了 Hmp1 = aprioriGen(H, m+1) # 返回可信度大於最小可信度的集合 Hmp1 = calcConf(freqSet, Hmp1, supportData, brl, minConf) print 'Hmp1=', Hmp1 print 'len(Hmp1)=', len(Hmp1), 'len(freqSet)=', len(freqSet) # 計算可信度后,還有數據大於最小可信度的話,那么繼續遞歸調用,否則跳出遞歸 if (len(Hmp1) > 1): print '----------------------', Hmp1 # print len(freqSet), len(Hmp1[0]) + 1 rulesFromConseq(freqSet, Hmp1, supportData, brl, minConf)
生成關聯規則
# 生成關聯規則 def generateRules(L, supportData, minConf=0.7): """generateRules Args: L 頻繁項集列表 supportData 頻繁項集支持度的字典 minConf 最小置信度 Returns: bigRuleList 可信度規則列表(關於 (A->B+置信度) 3個字段的組合) """ bigRuleList = [] # 假設 L = [[frozenset([1]), frozenset([3]), frozenset([2]), frozenset([5])], [frozenset([1, 3]), frozenset([2, 5]), frozenset([2, 3]), frozenset([3, 5])], [frozenset([2, 3, 5])]] for i in range(1, len(L)): # 獲取頻繁項集中每個組合的所有元素 for freqSet in L[i]: # 假設:freqSet= frozenset([1, 3]), H1=[frozenset([1]), frozenset([3])] # 組合總的元素並遍歷子元素,並轉化為 frozenset 集合,再存放到 list 列表中 H1 = [frozenset([item]) for item in freqSet] # 2 個的組合,走 else, 2 個以上的組合,走 if if (i > 1): rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf) else: calcConf(freqSet, H1, supportData, bigRuleList, minConf) return bigRuleList
到這里為止,通過調用 generateRules 函數即可得出我們所需的 關聯規則
。
- 分級法: 頻繁項集->關聯規則
- 最后: 每次增加頻繁項集的大小,Apriori 算法都會重新掃描整個數據集,是否有優化空間呢? 下一章:FP-growth算法等着你的到來