本節將會對於Apriori算法的頻繁集篩選過程,進行代碼的展示
1 回顧
上節提到,對於Apriori算法來說,其核心價值是在關聯分析的兩個過程,即篩選頻繁項集、關聯規則獲取的過程中,使得過程更加簡便。
首先,回顧一下Apriori算法中使得計算過程變得簡便的原理(Apriori原理):
- 如果某個項集是頻繁的,那麼它的所有子集也是頻繁的
- 反之,如果一個項集是非頻繁集,那麼 它的所有超集也是非頻繁的
此原理的論證部分,已在上文的結尾證明過。因此,我們現在需要考慮的問題就是,如何從原始數據集和出發,按照自己定義的”頻繁“程度,對項集進行篩選,得到頻繁項集。
2 篩選候選集
2.1 理論部分講解
我們很容易能夠想到,如果需要篩選,我們必須具有一個篩選前的總體部分。即,我們應該先從初始的以記錄形式存儲的數據集,轉化爲一個一個的項集。
這裏解釋一下項集的意思:以購買商品爲例,用戶一次購物,購買的商品A,B,C,D。這條記錄會以{商品A,商品B,商品C,商品D}的形式存儲在數據庫中,這樣的一條購買記錄稱爲一條記錄(或者事務)。我們用單詞縮寫tran(transaction)來表示。
而這條記錄的子集,例如{商品A},{商品B}等,我們稱爲一個項集
那麼首先,我們應該獲取所有的候選集。因爲我們沒有現成可用的數據庫,因此先自己定義一個數據集合
'''
@author:小湯
編寫內容:Apriori算法
'''
#函數loadDataset()
#用於返回需要尋找頻繁項集的數據集合
def loadDataset():
return [[1,3,4],[2,3,5],[1,2,3,5],[2,5]]
第二步:對於這樣的一個數據集合,我們通過什麼樣的步驟獲取其所有的組合呢?(在計算的過程中如何使用Apriori原理使得過程更加簡便?)
記錄 | 內容 |
---|---|
tran1 | [1,3,4] |
tran2 | [2,3,5] |
tran3 | [1,2,3,5] |
tran4 | [2,5] |
首先,對於tran1來說,我們能夠抽取出來的子集應該是{1},{3},{4},然後是這三個子集的組合{1,3},{1,4},{3,4},{1,3,4}.
如果按照這一暴力方式,我們需要對每條記錄都進行子集的獲取,並且在獲取完所有的子集後,假設以上數據集一共有n種組合方式,再對每一種組合方式(項集)進行支持度的計算。
這樣的工作量是很大的。因此我們需要在生成的過程中,使用Apriori原理,對中間結果進行剪枝處理。
因此,在處理中,就可以提出了一個層的概念,如果學過數據結構的同學,對於層的概念應該會比較好理解,即類似於樹的結構。對於這樣的一個數據集,應該有這樣的處理方式如下圖:
在這個過程中,使用到的迭代的思想,先生成只有一項的項集,計算支持度,不符合要求的直接剔除(也因此剔除了它的所有父集)。循環往復,最後就可以生成符合要求的頻繁集L。
接下來看代碼實現部分:
2.2 主體代碼部分
首先,我們根據上面的流程圖,整個算法實現應該至少需要幾個部分的功能函數,因此,按照流程和功能來分析就可以比較順利的完成所有的代碼:
2.2.1 創建C1層
輸入:原始數據集
輸出:只有一個項的項集(C1集)
邏輯:讀取數據集中的每一條記錄,獲取其中一項,若沒有記錄過,就存起來,若記錄過,就不存。
'''
@author:小湯
編寫內容:Apriori算法
'''
#創建C1集合
def createC1(dataset):
C1=[]
for transaction in dataset:#獲取數據集中的一條記錄
for item in transaction:#獲取記錄中的一個項集,這裏的項集都是1一個元素構成
if [item] not in C1: #書上寫的是if not [] in 這裏改成了更加符合字然語言習慣的寫法
C1.append([item]) #將元素作爲一個項集加入C1列表
C1.sort() #排序
return list(map(frozenset,C1))#冰凍集合,不可修改 map() 會根據提供的函數對指定序列做映射
#即對C1中的每個元素進行冰凍化
#frozenset的作用在於,這種集合可以放在另外一個集合當中。因爲set類型並不保證自己是不變的,滿足不了其超集的確定性
#這裏必須要 在map外添加list數據類型轉換,原因是在python3中map返回的不再是列表而是一個迭代器
重點注意python中frozenset的使用: frozenset的作用在於,這種集合可以放在另外一個集合當中。因爲set類型並不保證自己是不變的,滿足不了其超集的確定性
然後,就需要一個函數,在之後的過程中不斷的篩選滿足條件的項集列表,第一步中則是將C1層篩選爲C2層。
2.2.2 創建L1~LN層
輸入:數據集合D(注意要轉換爲集合類型),候選項集合Ck,最小支持度minSupport
輸出:滿足頻繁條件的項集
邏輯:對輸入的C1的每一個項集:
對數據庫的每條記錄:
檢索其是否出現在記錄中
如果出現過,則對其出現次數+1(使用字典ssCnt存儲)
如果沒有,則跳過
完成以上計數過程後,進行支持度判斷
對於ssCnt中的每一項集,均做支持度的判斷
如果符合頻繁條件,將其本身加入retList,否則不記錄
記錄每一個項集和其對應的支持度(後續關聯規則使用)
ps:個人認爲沒必要把每一個都存,只存頻繁集的就可以。不知作者這裏是不是寫錯了(因爲關聯規則只會用到頻繁集涉及到的元素的支持度)
'''
@author:小湯
編寫內容:Apriori算法
'''
#創建L1~LN集合,這一步計算進來的項集的支持度,返回滿足的。
def scanD(D,Ck,minSupport): #數據集合D(注意要轉換爲集合類型),候選項集合Ck,最小支持度minSupport
ssCnt={} #空字典 ssCnt,字典的鍵是項集,值是出現次數,之後要換成支持度
for tid in D: #遍歷數據集合 tid表示數據集中的一條記錄
for can in Ck: #對於候選集和Ck中的一個項集can來說,第一遍就是C1是Ck候選集,can是C1中的所有項集
if can.issubset(tid): #如果can是tid的一個子集(如果項集出現在記錄中)
if can not in ssCnt: #not ssCnt.has_key(can) 因爲has_key用法在python3中被移除了,所以改成此用法
ssCnt[can]=1 #這裏就是一個計數的判斷
else:
ssCnt[can]+=1
numItems = float(len(D)) #獲取數據集合的記錄長度
retList= [] #構建空列表用於存儲滿足最小支持度的項集
supportData = {}
for key in ssCnt: #計算和判斷支持度,遍歷所有的key
support=ssCnt[key]/numItems #ssCnt[key]是對應的值,除數量轉化成支持度
if support >=minSupport:
retList.append(key) #將頻繁集,插入首部,0代表在字頭插入
supportData[key]=support #將項集作爲key,支持度作爲value存入supportdata(每個項集都會被記錄)
return retList,supportData
至此,我們可以使用以上代碼生成C1層和L1層的項集。
2.2.3 生成排列組合的功能函數
在生成L1後,我們應該使用一個排列組合函數使得L1中的函數能夠自由的組合成C2項集
輸入:LN函數,k(表示生產的項集有多少個元素)
輸出:C (N+1)函數
在這一個函數中,我們需要特別注意有一個可以簡化的地方,具體可以研究以下實例:
利用{0,1}、 {0,2}、 {1,2}來創建三元素 項集,應該怎麼做?
如果將每兩個集合合併,就會得到{0, 1, 2}、 {0, 1, 2}、 {0, 1, 2}。也就是說,
同樣的結果集合會重複3次。接下來需要掃描三元素項集列表來得到非重複結果,我們要做的是 確保遍歷列表的次數最少
如果比較集合{0,1}、 {0,2}、 {1,2}的第1個元素並只對第1個元素相同的集合求並操作,則只需要操作一次,無需去重
這是因爲,在我們處理的過程中,每次需要進行組合的集合的元素個數都相同(且元素按照順序排列),因此,如果比較除最後一位元素的剩餘部分,若相同(只有最後一個元素不同),則可以進行一次合併。(如果無法理解,也可以遍歷取並集,再去重)
'''
@author:小湯
編寫內容:Apriori算法
'''
#這個函數中的k-2是爲了能夠減少重複次數
#因爲如果只是對所有的備選元素進行取並集的操作,還需要加入一個去重的操作
#如果只將最後一個元素不同的兩個集合進行合併,就不需要加入去重操作
def aprioriGen(Lk, k): #creates Ck,k表示生產的項集有多少個元素
retList = [] #空列表存儲Ck
lenLk = len(Lk)
for i in range(lenLk):#生成候選集
for j in range(i+1, lenLk):
L1 = list(Lk[i])[:k-2] #list(Lk[i]) 獲取了一個項集並轉化爲list
L2 = list(Lk[j])[:k-2] #這裏的L1和L2是前後兩個項集的第1~K-1項,因爲list計數從0開始,所以獲取K-2項
L1.sort()
L2.sort()
if L1==L2: #如果相同,即兩個集合中,有最後一個元素的不同,就可以合併成下一級項集
retList.append(Lk[i] | Lk[j]) #取並集
return retList
2.3 主函數
功能函數全部撰寫完畢,則可以加入驅動所有函數的主函數。按照流程圖來寫如下:
邏輯:創建C1—>創建L1—>排列組合—>創建C2—>…—>創建LN
#主函數
def apriori(dataSet, minSupport=0.5):
C1 = createC1(dataSet) #創建C1列表
D = list(map(set, dataSet)) #將數據集合變更爲集合類型
L1, supportData = scanD(D, C1, minSupport) #創建L1列表
L = [L1] #將L1放入一個列表L中,L1現在是第0層
k = 2 #L後續會包括L2~Ln
while (len(L[k-2]) > 0): #第一次運行while時:對L中的第0層也就是L1進行處理,後續以此類推
Ck = aprioriGen(L[k-2], k) #首先,L中存L1,L[2-2]即L[0]就是L1,計算L1的下一層組合,即返回的Ck
Lk, supK = scanD(D, Ck, minSupport) #再對組合Ck的項集進行支持度的計算,得到L某層,和支持度
supportData.update(supK) #對於supportData進行更新,即項集和它的支持度鍵值對
L.append(Lk) #將第2層~第N層的滿足項集加入L列表
k += 1 #進入下一層
return L, supportData
3 結果
對以上結果進行運行:
data=loadDataset()
L,s=apriori(data,0.5) #獲取頻繁集
輸出:
頻繁集:[[frozenset({1}), frozenset({3}), frozenset({2}), frozenset({5})], [frozenset({1, 3}), frozenset({2, 3}), frozenset({3, 5}), frozenset({2, 5})], [frozenset({2, 3, 5})], []]
項集和其對應的支持度:{frozenset({1}): 0.5, frozenset({3}): 0.75,
frozenset({4}):0.25, frozenset({2}): 0.75, frozenset({5}): 0.75, frozenset({1, 3}): 0.5, frozenset({2, 3}): 0.5, frozenset({3, 5}): 0.5, frozenset({2, 5}): 0.75,frozenset({1, 2}): 0.25,frozenset({1, 5}): 0.25,rozenset({2, 3, 5}): 0.5}
注意:以上就出現了一些不是頻繁集合的項集和其對應的支持度,個人在代碼部分已經提出了認爲沒必要存儲。測試後續關聯規則代碼時也並不影響計算。因此用刪除線畫出。不過大家確實可以看到,程序可以有效地把低於0.5支持度的項集篩選掉。
下一節:【機器學習】【無監督學習】【算法01-理論2】Apiori算法-關聯規則獲取
*以上代碼來自於參考《機器學習實戰》,本人手動撰寫並註釋。若有侵權,請聯繫刪除。