《機器學習實戰》使用Apriori演算法和FP-growth演算法進行關聯分析(Python版)
=====================================================================
《機器學習實戰》系列部落格是博主閱讀《機器學習實戰》這本書的筆記也包含一些其他python實現的機器學習演算法 演算法實現均採用python=====================================================================
1:關聯分析
2:Apriori演算法和FP-growth演算法原理
3:使用Apriori演算法發現頻繁項集
4:使用FP-growth高效發現頻繁項集
5:例項:從新聞站點點選流中挖掘新聞報道
以下程式用到的原始碼下載地址:
一:關聯分析
1:相關概念
關聯分析(association analysis):從大規模資料集中尋找商品的隱含關係
項集 (itemset):包含0個或者多個項的集合稱為項集
頻繁項集:那些經常一起出現的物品集合
支援度計數(support count):一個項集出現的次數也就是整個交易資料集中包含該項集的事物數
關聯規則是形如A->B的表示式,規則A->B的度量包括支援度和置信度
項集支援度:一個項集出現的次數與資料集所有事物數的百分比稱為項集的支援度
eg:support(A->B)=support_count(A並B) / N
項集置信度(confidence):資料集中同時包含A,B的百分比
eg:confidence(A->B) = support_count(A並B) / support_count(A)
2:關聯分析一些應用
(1):購物籃分析,通過檢視那些商品經常在一起出售,可以幫助商店瞭解使用者的購物行為,這種從資料的海洋中抽取只是可以用於商品定價、市場促銷、存貨管理等環節
(2):在Twitter源中發現一些公共詞。對於給定的搜尋詞,發現推文中頻繁出現的單詞集合
(3):從新聞網站點選流中挖掘新聞流行趨勢,挖掘哪些新聞廣泛被使用者瀏覽到
(4):搜尋引擎推薦,在使用者輸入查詢時推薦同時相關的查詢詞項
(5):發現毒蘑菇的相似特徵。這裡只對包含某個特徵元素(有毒素)的項集感興趣,從中尋找毒蘑菇中的一些公共特徵,利用這些特徵來避免遲到哪些有毒的蘑菇
3:樣例(下文分析所依據的樣本,交易資料表)
交易號碼 |
商品 |
100 |
Cola,Egg,Ham |
200 |
Cola,Diaper,Beer |
300 |
Cola,Diaper,Beer,Ham |
400 |
Diaper,Beer |
二:Apriori演算法和FP-growth演算法原理
1:Apriori演算法原理
找出所有可能是頻繁項集的項集,即候選項集,然後根據最小支援度計數刪選出頻繁項集,最簡單的辦法是窮舉法,即把每個項集都作為候選項集,統計他在資料集中出現的次數,如果出現次數大於最小支援度計數,則為頻繁項集。所有的可能的項集(E:Egg C:Cola D:Diaper B:Beer H:Ham) 頻繁項集的發現過程如下(假定給定的最小支援度為2):
頻繁項集的發現過程 經過剪枝後的圖為(紅色圓圈內即為剪枝去掉的部分):
剪枝後的候選集為(E:Egg C:Cola D:Diaper B:Beer H:Ham) 那麼CD,CB,CH,DB,DH,BH,DBH即為所求的候選集
2:FP-growth演算法原理
FP-growth演算法不同於Apriori演算法生成候選項集再檢查是否頻繁的”產生-測試“方法,而是使用一種稱為頻繁模式樹(FP-Tree,PF代表頻繁模式,Frequent Pattern)選單緊湊資料結構組織資料,並直接從該結構中提取頻繁項集,下面針對
交易號碼 |
商品 |
100 |
Cola,Egg,Ham |
200 |
Cola,Diaper,Beer |
300 |
Cola,Diaper,Beer,Ham |
400 |
Diaper,Beer |
根據上表構造的FP-Tree 在FP-Tree上挖掘頻繁模式: 挖掘FP-Tree採用自低向上的迭代模式,首先查詢以”Ham“為字尾的頻繁項集,然後依次是”Beer“,”Diaper“,”Cola“ 查詢以”Ham“為字尾的頻繁項集,首先在FP-Tree中找出所有包含”Ham“的記錄,利用頭節點表和樹節點的連結,找出包含”Ham“的兩個分支,<Cola:3,Ham:1>和<(Cola:3,Diaper:2,Beer:1,Ham:1)>,說明在該FP-Tree所代表的資料集中記錄(Cola,Ham)和(Cola,Diaper,Beer,Ham)各出現了一次,利用這兩個分支所代表的記錄構造”Ham“的條件模式基。條件模式基可以看作是一個“子資料集”,由FP-Tree中與字尾模式一起出現的字首路徑組成,Ham作為字尾模式時,”Ham“的兩個字首路徑{(Cola:1),(Cola Diaper Beer:1)}構成了”Ham“的條件模式基。利用”Ham“的條件模式基構造FP-TRee,即“Ham”的條件FP樹。“Ham ”的條件模式基中,Cola出現了2次,Diaper,Beer只出現了1次,所以Diaper,Beer是非頻繁項,不包含在“Ham”的條件模式樹中,“Ham”的條件模式樹只有一個分支<Cola:2>,得到條件頻繁項集{Cola:2},條件頻繁項集與字尾模式“Ham“合併,得到頻繁項集{Cola Ham :2} 同理查詢”Beer“為字尾的頻繁項集,得到{ {Diaper Beer :3} , {Cola Diaper Beer:2}, {Cola Beer:2} } 查詢”Diaper“為結尾的頻繁項集,得到 {Cola Diaper :2}
三:使用Apriori演算法發現頻繁項集和挖掘相關規則
1:發現頻繁項集
Apriori演算法是發現頻繁項集的一種方法。Apriori演算法的兩個輸入引數分別是最小支援度和資料集。該演算法首先會生成所有單個元素的項集列表。接著掃描資料集來檢視哪些項集滿足最小支援度要求,那些不滿足最小支援度的集合會被去掉。然後,對剩下來的集合進行組合以生成包含兩個元素的項集。接下來,再重新掃描交易記錄,去掉不滿足最小支援度的項集。該過程重複進行直到所有項集都被去掉,建立 Apriori.py檔案,加入以下程式碼#-*-coding:utf-8-*- ''' Created on 2016年5月8日 @author: Gamer Think ''' from pydoc import apropos #========================= 準備函式 (下) ========================================== #載入資料集 def loadDataSet(): return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]] def createC1(dataSet): C1 = [] #C1為大小為1的項的集合 for transaction in dataSet: #遍歷資料集中的每一條交易 for item in transaction: #遍歷每一條交易中的每個商品 if not [item] in C1: C1.append([item]) C1.sort() #map函式表示遍歷C1中的每一個元素執行forzenset,frozenset表示“冰凍”的集合,即不可改變 return map(frozenset,C1) #Ck表示資料集,D表示候選集合的列表,minSupport表示最小支援度 #該函式用於從C1生成L1,L1表示滿足最低支援度的元素集合 def scanD(D,Ck,minSupport): ssCnt = {} for tid in D: for can in Ck: #issubset:表示如果集合can中的每一元素都在tid中則返回true if can.issubset(tid): #統計各個集合scan出現的次數,存入ssCnt字典中,字典的key是集合,value是統計出現的次數 if not ssCnt.has_key(can): ssCnt[can] = 1 else: ssCnt[can] += 1 numItems = float(len(D)) retList = [] supportData = {} for key in ssCnt: #計算每個項集的支援度,如果滿足條件則把該項集加入到retList列表中 support = ssCnt[key]/numItems if support >= minSupport: retList.insert(0, key) #構建支援的項集的字典 supportData[key] = support return retList,supportData #==================== 準備函式(上) ============================= #====================== Apriori演算法(下) ================================= #Create Ck,CaprioriGen ()的輸人蔘數為頻繁項集列表Lk與項集元素個數k,輸出為Ck def aprioriGen(Lk,k): retList = [] lenLk = len(Lk) for i in range(lenLk): for j in range(i+1,lenLk): #前k-2項相同時合併兩個集合 L1 = list(Lk[i])[:k-2] L2 = list(Lk[j])[:k-2] L1.sort() L2.sort() if L1 == L2: retList.append(Lk[i] | Lk[j]) return retList def apriori(dataSet, minSupport=0.5): C1 = createC1(dataSet) #建立C1 #D: [set([1, 3, 4]), set([2, 3, 5]), set([1, 2, 3, 5]), set([2, 5])] D = map(set,dataSet) L1,supportData = scanD(D, C1, minSupport) L = [L1] #若兩個項集的長度為k - 1,則必須前k-2項相同才可連線,即求並集,所以[:k-2]的實際作用為取列表的前k-1個元素 k = 2 while(len(L[k-2]) > 0): Ck = aprioriGen(L[k-2], k) Lk,supK = scanD(D,Ck, minSupport) supportData.update(supK) L.append(Lk) k +=1 return L,supportData #====================== Apriori演算法(上) ================================= if __name__=="__main__": dataSet = loadDataSet() L,suppData = apriori(dataSet) i = 0 for one in L: print "項數為 %s 的頻繁項集:" % (i + 1), one,"\n" i +=1 |
2:挖掘相關規則
要找到關聯規則,我們首先從一個頻繁項集開始。從文章開始說的那個交易資料表的例子可以得到,如果有一個頻繁項集{Diaper,Beer},那麼就可能有一條關聯規則“Diaper➞Beer”。這意味著如果有人購買了Diaper,那麼在統計上他會購買Beer的概率較大。注意這一條反過來並不總是成立,也就是說,可信度(“Diaper➞Beer”)並不等於可信度(“Beer➞Diaper”)。前文也提到過,一條規則P➞H的可信度定義為support(P 並 H)/support(P)。可見可信度的計算是基於項集的支援度的。
下圖給出了從項集{0,1,2,3}產生的所有關聯規則,其中陰影區域給出的是低可信度的規則。可以發現如果{0,1,2}➞{3}是一條低可信度規則,那麼所有其他以3作為後件(箭頭右部包含3)的規則均為低可信度的。
頻繁項集{0,1,2,3}的關聯規則網格示意圖
可以觀察到,如果某條規則並不滿足最小可信度要求,那麼該規則的所有子集也不會滿足最小可信度要求。以圖4為例,假設規則{0,1,2} ➞ {3}並不滿足最小可信度要求,那麼就知道任何左部為{0,1,2}子集的規則也不會滿足最小可信度要求。可以利用關聯規則的上述性質屬性來減少需要測試的規則數目,類似於Apriori演算法求解頻繁項集。
關聯規則生成函式:
def generateRules(L, supportData, minConf=0.7):
bigRuleList = []
for i in range(1,
len(L)):
for freqSet in L[i]:
H1 = [frozenset([item]) for item in freqSet]
if (i
> 1):
# 三個及以上元素的集合
rulesFromConseq(freqSet, H1, supportData, bigRuleList, minConf)
else :
# 兩個元素的集合
calcConf(freqSet, H1, supportData, bigRuleList, minConf)
return bigRuleList
|
這個函式是主函式,呼叫其他兩個函式。其他兩個函式是rulesFromConseq()和calcConf(),分別用於生成候選規則集合以及對規則進行評估(計算支援度)。
函式generateRules()有3個引數:頻繁項集列表L、包含那些頻繁項集支援資料的字典supportData、最小可信度閾值minConf。函式最後要生成一個包含可信度的規則列表bigRuleList,後面可以基於可信度對它們進行排序。L和supportData正好為函式apriori()的輸出。該函式遍歷L中的每一個頻繁項集,並對每個頻繁項集構建只包含單個元素集合的列表H1。程式碼中的i指示當前遍歷的頻繁項集包含的元素個數,freqSet為當前遍歷的頻繁項集(回憶L的組織結構是先把具有相同元素個數的頻繁項集組織成列表,再將各個列表組成一個大列表,所以為遍歷L中的頻繁項集,需要使用兩層for迴圈)。
輔助函式——計算規則的可信度,並過濾出滿足最小可信度要求的規則
def calcConf(freqSet, H, supportData, brl, minConf = 0.7 ):
''' 對候選規則集進行評估 '''
prunedH = []
for conseq in H:
conf = supportData[freqSet] / supportData[freqSet - conseq]
if conf
> = minConf:
print freqSet - conseq, '-->' ,
conseq, 'conf:' , conf
brl.append((freqSet - conseq,
conseq, conf))
prunedH.append(conseq)
return prunedH
|
計算規則的可信度以及找出滿足最小可信度要求的規則。函式返回一個滿足最小可信度要求的規則列表,並將這個規則列表新增到主函式的bigRuleList中(通過引數brl)。返回值prunedH儲存規則列表的右部,這個值將在下一個函式rulesFromConseq()中用到。
輔助函式——根據當前候選規則集H生成下一層候選規則集
def rulesFromConseq(freqSet, H, supportData, brl, minConf = 0.7 ):
''' 生成候選規則集 '''
m = len (H[ 0 ])
if ( len (freqSet)
> (m + 1 )):
Hmpl = aprioriGen(H,
m + 1 )
Hmpl = calcConf(freqSet,
Hmpl, supportData, brl, minConf)
if ( len (Hmpl)
> 1 ):
rulesFromConseq(freqSet, Hmpl, supportData, brl, minConf)
|
從最初的項集中生成更多的關聯規則。該函式有兩個引數:頻繁項集freqSet,可以出現在規則右部的元素列表H。其餘引數:supportData儲存項集的支援度,brl儲存生成的關聯規則,minConf同主函式。函式先計算H中的頻繁項集大小m。接下來檢視該頻繁項集是否大到可以移除大小為m的子集。如果可以的話,則將其移除。使用函式aprioriGen()來生成H中元素的無重複組合,結果儲存在Hmp1中,這也是下一次迭代的H列表。
將上邊的三個函式加入 Apriori.py中 main函式修改為:if __name__=="__main__": dataSet = loadDataSet() L,suppData = apriori(dataSet) i = 0 for one in L: print "項數為 %s 的頻繁項集:" % (i + 1), one,"\n" i +=1 print "minConf=0.7時:" rules = generateRules(L,suppData, minConf=0.7) print "\nminConf=0.5時:" rules = generateRules(L,suppData, minConf=0.5) |
關於rulesFromConseq()函式的問題
如果仔細看下上述程式碼和輸出,會發現這裡面是一些問題的。
1 問題的提出
頻繁項集L的值前面提到過。我們在其中計算通過{2, 3, 5}生成的關聯規則,可以發現關聯規則{3, 5}➞{2}和{2, 3}➞{5}的可信度都應該為1.0的,因而也應該包括在當minConf = 0.7時的rules中——但是這在前面的執行結果中並沒有體現出來。minConf = 0.5時也是一樣,{3, 5}➞{2}的可信度為1.0,{2, 5}➞{3}的可信度為2/3,{2, 3}➞{5}的可信度為1.0,也沒有體現在rules中。
通過分析程式程式碼,我們可以發現:
- 當i = 1時,generateRules()函式直接呼叫了calcConf()函式直接計算其可信度,因為這時L[1]中的頻繁項集均包含兩個元素,可以直接生成和判斷候選關聯規則。比如L[1]中的{2, 3},生成的候選關聯規則為{2}➞{3}、{3}➞{2},這樣就可以了。
- 當i > 1時,generateRules()函式呼叫了rulesFromConseq()函式,這時L[i]中至少包含3個元素,如{2, 3, 5},對候選關聯規則的生成和判斷的過程需要分層進行(圖4)。這裡,將初始的H1(表示初始關聯規則的右部,即箭頭右邊的部分)作為引數傳遞給了rulesFromConseq()函式。
例如,對於頻繁項集{a, b, c, …},H1的值為[a, b, c, …](程式碼中實際為frozenset型別)。如果將H1帶入計算可信度的calcConf()函式,在函式中會依次計算關聯規則{b, c, d, …}➞{a}、{a, c, d, …}➞{b}、{a, b, d, …}➞{c}……的支援度,並儲存支援度大於最小支援度的關聯規則,並儲存這些規則的右部(prunedH,即對H的過濾,刪除支援度過小的關聯規則)。
當i > 1時沒有直接呼叫calcConf()函式計算通過H1生成的規則集。在rulesFromConseq()函式中,首先獲得當前H的元素數m = len(H[0])(記當前的H為Hm )。當Hm可以進一步合併為m+1元素數的集合Hm+1時(判斷條件:len(freqSet) > (m + 1)),依次:
- 生成Hm+1:Hmpl = aprioriGen(H, m + 1)
- 計算Hm+1的可信度:Hmpl = calcConf(freqSet, Hmpl, …)
- 遞迴計算由Hm+1生成的關聯規則:rulesFromConseq(freqSet, Hmpl, …)
所以這裡的問題是,在i>1時,rulesFromConseq()函式中並沒有呼叫calcConf()函式計算H1的可信度,而是直接由H1生成H2,從H2開始計算關聯規則——於是由元素數>3的頻繁項集生成的{a, b, c, …}➞{x}形式的關聯規則(圖4中的第2層)均缺失了。由於程式碼示例資料中的對H1的剪枝prunedH沒有刪除任何元素,結果只是“巧合”地缺失了一層。正常情況下如果沒有對H1進行過濾,直接生成H2,將給下一層帶入錯誤的結果(如圖4中的012➞3會被錯誤得留下來)。
在i>1時,將對H1呼叫calcConf()的過程加上就可以了。比如可以這樣:
def generateRules2(L, supportData, minConf
|