
01 啤酒與尿布
好久沒寫代碼了,腦子快生銹了,今天我們來實操一個比較有意思的算法——Apriori算法。
Apriori算法是一種用于挖掘數(shù)據(jù)集內(nèi)部關(guān)聯(lián)規(guī)則的算法,“apriori”在拉丁語中翻譯為“來自以前”,聽意思你應(yīng)該就能猜到了,這個算法是用先驗知識來預(yù)測數(shù)據(jù)的關(guān)聯(lián)規(guī)則的。
說到關(guān)聯(lián)規(guī)則,有一個很有名的案例——啤酒與尿布。說,美國一家連鎖店發(fā)現(xiàn)很多男性會在周四購買尿布和啤酒,這兩種看似不相干的商品之間顯現(xiàn)出強相關(guān)性,于是商家可以將啤酒貨架放在尿布貨架旁邊以增加收益。
那么,啤酒與尿布的關(guān)系是如何被發(fā)現(xiàn)的呢?當(dāng)然是通過關(guān)聯(lián)算法,我們從Apriori算法開始吧,利用Apriori進(jìn)行關(guān)聯(lián)分析。
02 Apriori原理
先介紹兩個概念,
- 支持度support:數(shù)據(jù)集中包含該項集的數(shù)據(jù)所占數(shù)據(jù)集的比例,度量一個集合在原始數(shù)據(jù)中出現(xiàn)的頻率
- 置信度confidence:是針對一條關(guān)聯(lián)規(guī)則來定義的,a->b的置信度=支持度{a|b}/支持度{a},a|b表示ab的并集
關(guān)聯(lián)分析有兩個目標(biāo):
- 發(fā)現(xiàn)頻繁項集(頻繁項集是滿足最小支持度要求的項集,它給出經(jīng)常在一起出現(xiàn)的元素項)
- 發(fā)現(xiàn)關(guān)聯(lián)規(guī)則(關(guān)聯(lián)規(guī)則意味著元素項之間“如果…那么…”的關(guān)系)
Apriori原理,
- 如果某個項集是頻繁的,那么它的所有子集也是頻繁的
- 如果某個項集是非頻繁的,那么它的所有超集也是非頻繁的
- 基于此,Apriori算法從單元素項集開始,通過組合滿足最小支持度的項集來形成更大的集合
其實Apriori就是通過排除法來選擇頻繁項集和關(guān)聯(lián)規(guī)則,下面我們根據(jù)這樣的原理用python實現(xiàn)算法。
03 Apriori代碼實操
3.1 挖掘頻繁項集
挖掘頻繁項集的邏輯如下圖

#加載數(shù)據(jù)集
def loadDataSet():
return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]
#選取數(shù)據(jù)集的非重復(fù)元素組成候選集的集合C1
def createC1(dataSet):
C1=[]
for transaction in dataSet: #對數(shù)據(jù)集中的每條購買記錄
for item in transaction: #對購買記錄中的每個元素
if [item] not in C1: #注意,item外要加上[],便于與C1中的[item]對比
C1.append([item])
C1.sort()
return list(map(frozenset,C1)) #將C1各元素轉(zhuǎn)換為frozenset格式,注意frozenset作用對象為可迭代對象
#由Ck產(chǎn)生Lk:掃描數(shù)據(jù)集D,計算候選集Ck各元素在D中的支持度,選取支持度大于設(shè)定值的元素進(jìn)入Lk
def scanD(D,Ck,minSupport):
ssCnt={}
for tid in D: #對數(shù)據(jù)集中的每條購買記錄
for can in Ck: #遍歷Ck所有候選集
if can.issubset(tid): #如果候選集包含在購買記錄中,計數(shù)+1
ssCnt[can]=ssCnt.get(can,0)+1
numItems=float(len(D)) #購買記錄數(shù)
retList=[] #用于存放支持度大于設(shè)定值的項集
supportData={} #用于記錄各項集對應(yīng)的支持度
for key in ssCnt.keys():
support=ssCnt[key]/numItems
if support>=minSupport:
retList.insert(0,key)
supportData[key]=support
return retList,supportData
#由Lk產(chǎn)生Ck+1
def aprioriGen(Lk,k): #Lk的k和參數(shù)k不是同一個概念,Lk的k比參數(shù)k小1
retList=[]
lenLk=len(Lk)
for i in range(lenLk):
for j in range(i+1,lenLk): #比較Lk中的每一個元素與其他元素
L1=list(Lk[i])[:k-2];L2=list(Lk[j])[:k-2]
L1.sort();L2.sort()
if L1==L2: #若前k-2項相同,則合并這兩項
retList.append(Lk[i]|Lk[j])
return retList
#Apriori算法主函數(shù)
def apriori(dataSet,minSupport=0.5):
C1=createC1(dataSet)
D=list(map(set,dataSet))
L1,supportData=scanD(D,C1,minSupport)
L=[L1]
k=2
while len(L[k-2])>0: #當(dāng)L[k]為空時,停止迭代
Ck=aprioriGen(L[k-2],k) #L[k-2]對應(yīng)的值是Lk-1
Lk,supK=scanD(D,Ck,minSupport)
supportData.update(supK)
L.append(Lk)
k+=1
return L,supportData
我們來測試一下,
dataset=loadDataSet()
C1=createC1(dataset)
D=list(map(set,dataset))
L1,supportData0=scanD(D,C1,0.5)
L,supportData=apriori(dataset,minSupport=0.5)

可以看到,頻繁項集如上圖,{1,2,3,5,{2,3},{3,5},{2,5},{1,3},{2,3,5}}都是頻繁項集。得到了頻繁項集,接下來我們看看頻繁項集之間的關(guān)聯(lián)規(guī)則。
3.2 從頻繁項集挖掘關(guān)聯(lián)規(guī)則
挖掘關(guān)聯(lián)規(guī)則原理如下:若某條規(guī)則不滿足最小置信度要求,則該規(guī)則的所有子集也不滿足最小置信度要求
# 主函數(shù),由頻繁項集以及對應(yīng)的支持度,得到各條規(guī)則的置信度,選擇置信度滿足要求的規(guī)則為關(guān)聯(lián)規(guī)則
# 為了避免將所有數(shù)據(jù)都對比一遍,采用與上述相同的邏輯減少計算量——一層一層計算篩選
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] # H1是頻繁項集單元素列表,是關(guān)聯(lián)規(guī)則中a->b的b項
if i>1:
rulesFromConseq(freqSet,H1,supportData,bigRuleList,minConf)
else:
calConf(freqSet,H1,supportData,bigRuleList,minConf)
return bigRuleList
# 置信度計算函數(shù)
def calConf(freqSet,H,supportData,brl,minConf=0.7):
prunedH=[] # 用于存放置信度滿足要求的關(guān)聯(lián)規(guī)則的b項,即“提純后的H”
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
# 關(guān)聯(lián)規(guī)則合并函數(shù)
def rulesFromConseq(freqSet,H,supportData,brl,minConf=0.7):
m=len(H[0])
if len(freqSet)>(m+1): #查看頻繁項集freqSet是否大到可以移除大小為m的子集
Hmp1=aprioriGen(H,m+1) # 從Hm合并Hm+1
Hmp1=calConf(freqSet,Hmp1,supportData,brl,minConf)
if len(Hmp1)>1: #若合并后的Hm+1的元素大于1個,則繼續(xù)合并
rulesFromConseq(freqSet,Hmp1,supportData,brl,minConf)
測試一下,
L,supportData=apriori(dataset,minSupport=0.5)
rules=generateRules(L,supportData,minConf=0.5)

可以看到,如果有5那么必定有2,如果有3,那么66.7%的可能性有2,5……
04 總結(jié)
本文簡述關(guān)聯(lián)分析算法Apriori算法的原理,然后用python3進(jìn)行了實操,需要注意的是,Apriori算法的缺點——每次增加頻繁項集大小時(即Ck->Lk時),算法需要重新掃描整個數(shù)據(jù)集,當(dāng)數(shù)據(jù)集很大時,算法效率很低。
解決方法是FP-Growth算法,這個算法我們下一次講解。
05 參考
《機器學(xué)習(xí)實戰(zhàn)》 Peter Harrington Chapter11