資料探勘中的模式發現(二)Apriori演算法
基本概念
對於
支援度(support):
P(A∩B) ,既有A又有B的概率置信度(Confidence Strength):
conf(A→B)=sup(A∪B)sup(A)=P(B|A) 即,在A發生的事件中同時發生B的概率
例如購物籃分析:牛奶
⇒ 麵包例子:[支援度:3%,置信度:40%]
支援度3%:意味著3%顧客同時購買牛奶和麵包
置信度40%:意味著購買牛奶的顧客40%也購買麵包
候選集(Candidate itemset):
通過向下合併得出的項集。
定義為C[k]。
頻繁集(Frequent itemset):
支援度大於等於特定的最小支援度(Minimum Support/minsup)的項集。表示為L[k]。
提升比率(提升度Lift):
lift(X→Y)=lift(Y→X)=conf(X→Y)supp(Y)=conf(Y→X)supp(X)=P(X⋂Y)P(X)P(Y) 向下封閉屬性(Downward Closure Property)
如果一個項集滿足某個最小支援度要求,那麼這個項集的任何非空子集必須都滿足這個最小支援度。
Apriori演算法簡介
Apriori演算法是一種挖掘關聯規則的頻繁項集演算法,其核心思想是通過候選集生成向下封閉、檢測兩個階段來挖掘頻繁項集。
Apriori演算法應用廣泛,可用於消費市場價格分析,猜測顧客的消費習慣;網路安全領域中的入侵檢測技術;可用在用於高校管理中,根據挖掘規則可以有效地輔助學校管理部門有針對性的開展貧困助學工作;也可用在行動通訊領域中,指導運營商的業務運營和輔助業務提供商的決策制定。
挖掘步驟:
1.依據支援度找出所有頻繁項集(頻度)
2.依據置信度產生關聯規則(強度)
實現步驟
Apriori使用一種稱作逐層搜尋的迭代方法,“K-1項集”用於搜尋“K項集”。
首先,掃描整個事務,找出頻繁1-項集的集合,該集合記作L1。L1用於找頻繁“2項集”的集合L2,而L2用於找L3。如此下去,直到不能找到“K項集”。找每個Lk都需要一次資料庫掃描。
核心思想是:連線步和剪枝步。連線步是自連線,原則是保證前k-2項相同,並按照字典順序連線。剪枝步,是使任一頻繁項集的所有非空子集也必須是頻繁的。反之,如果某個候選的非空子集不是頻繁的,那麼該候選肯定不是頻繁的,從而可以將其從CK中刪除。
簡單的講,1、發現頻繁項集,過程為(1)掃描(2)計數(3)比較(4)產生頻繁項集(5)連線、剪枝,產生候選項集 重複步驟(1)~(5)直到不能發現更大的頻集
產生關聯規則
根據前面提到的置信度的定義,關聯規則的產生如下:
(1)對於每個頻繁項集L,產生L的所有非空子集;
(2)對於L的每個非空子集S,如果
則輸出規則
注:L-S表示在項集L中除去S子集的項集
虛擬碼
虛擬碼描述:
// 找出頻繁 1 項集
L1 =find_frequent_1-itemsets(D);
For(k=2;Lk-1 !=null;k++){
// 產生候選,並剪枝
Ck =apriori_gen(Lk-1 );
// 掃描 D 進行候選計數
For each 事務t in D{
Ct =subset(Ck,t); // 得到 t 的子集
For each 候選 c 屬於 Ct
c.count++;
}
//返回候選項集中不小於最小支援度的項集
Lk ={c 屬於 Ck | c.count>=min_sup}
}
Return L= 所有的頻繁集;
第一步:連線(join)
Procedure apriori_gen (Lk-1 :frequent(k-1)-itemsets)
For each 項集 l1 屬於 Lk-1
For each 項集 l2 屬於 Lk-1
If( (l1 [1]=l2 [1])&&( l1 [2]=l2 [2])&& ……&& (l1 [k-2]=l2 [k-2])&&(l1 [k-1]<l2 [k-1]) )
then{
c = l1 連線 l2 // 連線步:產生候選
//若k-1項集中已經存在子集c則進行剪枝
if has_infrequent_subset(c, Lk-1 ) then
delete c; // 剪枝步:刪除非頻繁候選
else add c to Ck;
}
Return Ck;
第二步:剪枝(prune)
Procedure has_infrequent_sub (c:candidate k-itemset; Lk-1 :frequent(k-1)-itemsets)
For each (k-1)-subset s of c
If s 不屬於 Lk-1 then
Return true;
Return false;
程式碼
#coding:utf-8
samples = [
["I1","I2","I5"],
["I2","I4"],
["I2","I3"],
["I1","I2","I4"],
["I1","I3"],
["I2","I3"],
["I1","I3"],
["I1","I2","I3","I5"],
["I1","I2","I3"]
]
min_support = 2
min_confidence = 0.6
fre_list = list()
def get_c1():
global record_list
global record_dict
new_dict = dict()
for row in samples:
for item in row:
if item not in fre_list:
fre_list.append(item)
new_dict[item] = 1
else:
new_dict[item] = new_dict[item] + 1
fre_list.sort()
print "candidate set:"
print_dict(new_dict)
for key in fre_list:
if new_dict[key] < min_support:
del new_dict[key]
print "after pruning:"
print_dict(new_dict)
record_list = fre_list
record_dict = record_dict
def get_candidateset():
new_list = list()
#自連線
for i in range(0,len(fre_list)):
for j in range(0,len(fre_list)):
if i == j:
continue
#如果兩個k項集可以自連線,必須保證它們有k-1項是相同的
if has_samesubitem(fre_list[i],fre_list[j]):
curitem = fre_list[i] + ',' + fre_list[j]
curitem = curitem.split(",")
curitem = list(set(curitem))
curitem.sort()
curitem = ','.join(curitem)
#如果一個k項集要成為候選集,必須保證它的所有子集都是頻繁的
if has_infresubset(curitem) == False and already_constains(curitem,new_list) == False:
new_list.append(curitem)
new_list.sort()
return new_list
def has_samesubitem(str1,str2):
str1s = str1.split(",")
str2s = str2.split(",")
if len(str1s) != len(str2s):
return False
nums = 0
for items in str1s:
if items in str2s:
nums += 1
str2s.remove(items)
if nums == len(str1s) - 1:
return True
else:
return False
def judge(candidatelist):
# 計算候選集的支援度
new_dict = dict()
for item in candidatelist:
new_dict[item] = get_support(item)
print "candidate set:"
print_dict(new_dict)
#剪枝
#頻繁集的支援度要大於最小支援度
new_list = list()
for item in candidatelist:
if new_dict[item] < min_support:
del new_dict[item]
continue
else:
new_list.append(item)
global fre_list
fre_list = new_list
print "after pruning:"
print_dict(new_dict)
return new_dict
def has_infresubset(item):
# 由於是逐層搜尋的,所以對於Ck候選集只需要判斷它的k-1子集是否包含非頻繁集即可
subset_list = get_subset(item.split(","))
for item_list in subset_list:
if already_constains(item_list,fre_list) == False:
return True
return False
def get_support(item,splitetag=True):
if splitetag:
items = item.split(",")
else:
items = item.split("^")
support = 0
for row in samples:
tag = True
for curitem in items:
if curitem not in row:
tag = False
continue
if tag:
support += 1
return support
def get_fullpermutation(arr):
if len(arr) == 1:
return [arr]
else:
newlist = list()
for i in range(0,len(arr)):
sublist = get_fullpermutation(arr[0:i]+arr[i+1:len(arr)])
for item in sublist:
curlist = list()
curlist.append(arr[i])
curlist.extend(item)
newlist.append(curlist)
return newlist
def get_subset(arr):
newlist = list()
for i in range(0,len(arr)):
arr1 = arr[0:i]+arr[i+1:len(arr)]
newlist1 = get_fullpermutation(arr1)
for newlist_item in newlist1:
newlist.append(newlist_item)
newlist.sort()
newlist = remove_dumplicate(newlist)
return newlist
def remove_dumplicate(arr):
newlist = list()
for i in range(0,len(arr)):
if already_constains(arr[i],newlist) == False:
newlist.append(arr[i])
return newlist
def already_constains(item,curlist):
import types
items = list()
if type(item) is types.StringType:
items = item.split(",")
else:
items = item
for i in range(0,len(curlist)):
curitems = list()
if type(curlist[i]) is types.StringType:
curitems = curlist[i].split(",")
else:
curitems = curlist[i]
if len(set(items)) == len(curitems) and len(list(set(items).difference(set(curitems)))) == 0:
return True
return False
def print_dict(curdict):
keys = curdict.keys()
keys.sort()
for curkey in keys:
print "%s:%s"%(curkey,curdict[curkey])
# 計算關聯規則的方法
def get_all_subset(arr):
rtn = list()
while True:
subset_list = get_subset(arr)
stop = False
for subset_item_list in subset_list:
if len(subset_item_list) == 1:
stop = True
rtn.append(subset_item_list)
if stop:
break
return rtn
def get_all_subset(s):
from itertools import combinations
return sum(map(lambda r: list(combinations(s, r)), range(1, len(s)+1)), [])
def cal_associative_rule(frelist):
rule_list = list()
rule_dict = dict()
for fre_item in frelist:
fre_items = fre_item.split(",")
subitem_list = get_all_subset(fre_items)
for subitem in subitem_list:
# 忽略為為自身的子集
if len(subitem) == len(fre_items):
continue
else:
difference = set(fre_items).difference(subitem)
rule_list.append("^".join(subitem)+"->"+"^".join(difference))
print "The rule is:"
for rule in rule_list:
conf = cal_rule_confidency(rule)
print rule,conf
if conf >= min_confidence:
rule_dict[rule] = conf
print "The associative rule is:"
for key in rule_list:
if key in rule_dict.keys():
print key,":",rule_dict[key]
def cal_rule_confidency(rule):
rules = rule.split("->")
support1 = get_support("^".join(rules),False)
support2 = get_support(rules[0],False)
if support2 == 0:
return 0
rule_confidency = float(support1)/float(support2)
return rule_confidency
if __name__ == '__main__':
record_list = list()
record_dict = dict()
get_c1()
# 不斷進行自連線和剪枝,直到得到最終的頻繁集為止;終止條件是,如果自連線得到的已經不再是頻繁集
# 那麼取最後一次得到的頻繁集作為結果
while True:
record_list = fre_list
new_list = get_candidateset()
judge_dict = judge(new_list)
if len(judge_dict) == 0:
break
else:
record_dict = judge_dict
print "The final frequency set is:"
print record_list
# 根據頻繁集計算關聯規則
cal_associative_rule(record_list)
Apriori演算法的改進
- 基於雜湊(Hash)的方法
1995年,Park等提出了一種基於雜湊(Hash)技術產生頻繁項集的演算法。這種方法把掃描的專案放到不同的Hash桶中,每個頻繁項最多隻能放在一個特定的桶裡,這樣可以對每個桶中的頻繁項自己進行測試,減少了候選頻繁項集產生的代價。
- 事務壓縮
事務壓縮是指壓縮未來迭代掃描的事務數。由於不包含任何頻繁k-項集的事務是不可能包含任何頻繁(k+1)-項集的,因此這種事務在後續的考慮中可以加上標記或者直接刪除,因此產生j-項集(j>k)的資料庫掃描不再需要它們。
- 基於資料劃分(Partition)的方法
Apriori演算法在執行過程中首先生成候選集,然後再進行剪枝。可是生成的候選集並不都是有效的,有些候選集根本不是事務資料的專案集。因此,候選集的產生具有很大的代價。特別是記憶體空間不夠導致資料庫與記憶體之間不斷交換資料,會使演算法的效率變得很差。
使用劃分方法,則挖掘頻繁項集只需要進行兩次掃描:
第一次:將資料劃分到多個部分並找到區域性頻繁項集。(把大容量資料庫從邏輯上分成幾個互不相交的塊,同時保證劃分的項集能夠完整地存入記憶體而不必進行I/O操作。每部分應用挖掘演算法(如Apriori演算法)生成區域性的頻繁項集,然後把這些區域性的頻繁項集作為候選的全域性頻繁專案集。)
第二次:評估每個候選項集的實際支援度,以確定全域性頻繁項集。
- 基於取樣(Sampling)的方法
基於取樣的方法是Toivonen於1996年提出的,這個演算法的基本思想是:選取給定資料D的隨機樣本S,然後在S而不是D中搜索頻繁項集。用這種方法是犧牲了一些精度換取有效性。樣本S的大小選取使得可以在記憶體搜尋S中的頻繁項集。這樣只需要掃描一次S中的事務。由於演算法只是搜尋S中的資料,因此可能會丟失一些全域性頻繁項集。為了減少這樣的情況,使用比最小支援度低的支援度閾值來找出區域性於S的頻繁項集(記做LS)。然後,資料庫的其餘部分用於計算LS中每個項集的實際頻率。使用一種機制來確定是否所有的頻繁項集都包含在LS中。如果LS實際包含了D中的所有頻繁項集,則只需掃描一次D。否則,可以做第二次掃描來找出第一次掃描時遺漏的頻繁項集。