一些基本概念
支持度:support(A=>B)=P(A並B)
置信度:confidence(A=>B)=P(B|A)
頻繁k項集:假設項集I的支持度滿足提前定義的最小支持度閾值。則稱I為頻繁項集,包括k個項的項集稱為k項集。
算法思想
Apriori算法是Agrawal和R. Srikant於1994年提出。為布爾關聯規則挖掘頻繁項集的原創性算法。
通過名字能夠看出算法基於這樣一個事實:算法使用頻繁項集性質的先驗知識。
apriori算法使用一種成為逐層搜索的迭代算法,當中k項集用於探索(k+1)項集。首先,通過掃描數據庫,累計每一個項的計數。並搜集滿足最小支持度的項,找出頻繁1項集的集合。該集合記為L1。然后,使用L1找出頻繁2項集的集合L2,使用L2找出L3。如此下去,直到不能再找到頻繁k項集。
能夠想象。該算法基本思路計算復雜度是很大的。為了提高頻繁項集的產生效率,使用先驗性質(頻繁項集的全部非空子集也一定是頻繁的;換句話說。若某個集合存在一個非空子集不是頻繁項集,則該集合不是頻繁項集)來壓縮搜索空間。
怎樣在算法中使用先驗性質?為了理解這一點。我們考察怎樣使用Lk-1找出Lk,當中k>=2。
主要由兩步構成:連接步和剪枝步。
連接步:為找出Lk。通過將Lk-1與自身相連接產生候選集k項集的集合。
該候選集的集合記為Ck。設l1和l2是Lk-1中的項集。記號li[j]表示li的第j項(比如。l1[k-2]表示l1的倒數第2項)。為了有效實現。apriori算法假定事務或項集中的項按字典序排列。
對於(k-1)項集li,這意味着把項排序,使得li[1]<li[2]<...<li[k-1]。連接Lk-1和Lk-1;當中Lk-1的元素是可連接的。假設它們前(k-2)項同樣。即Lk-1的元素l1和l2是可連接的,假設(l1[1]=l2[1])^(l1[2]=l2[2])^...^(l1[k-2]=l2[k-2])^(l1[k-1]<l2[k-1])。條件l1[k-1]<l2[k-1]是簡單保證不產生反復。連接l1和l2產生的結果項集是{l1[1],l1[2],...,l1[k-1],l2[k-1]}
剪枝步: CK是LK的超集,也就是說,CK的成員可能是也可能不是頻繁的。
通過掃描全部的事務(交易),確定CK中每一個候選的計數,推斷是否小於最小支持度計數,假設不是。則覺得該候選是頻繁的。為了壓縮Ck,能夠利用Apriori性質:任一頻繁項集的全部非空子集也必須是頻繁的,反之,假設某個候選的非空子集不是頻繁的,那么該候選肯定不是頻繁的,從而能夠將其從CK中刪除。
(該步利用了標紅的先驗性質)
圖例
偽代碼
算法:Apriori 輸入:D - 事務數據庫;min_sup - 最小支持度計數閾值 輸出:L - D中的頻繁項集 方法: L1=find_frequent_1-itemsets(D); // 找出全部頻繁1項集 For(k=2;Lk-1!=null;k++){ Ck=apriori_gen(Lk-1); // 產生候選,並剪枝 For each 事務t in D{ // 掃描D進行候選計數 Ct =subset(Ck,t); // 得到t的子集 For each 候選c 屬於 Ct c.count++; } Lk={c屬於Ck | c.count>=min_sup} } Return L=全部的頻繁集; 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 //連接步:產生候選 if has_infrequent_subset(c,Lk-1) then delete c; //剪枝步:刪除非頻繁候選 else add c to Ck; } Return Ck; 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;
Java實現
該java代碼基本上是嚴格依照偽代碼的流程寫的。比較easy理解。
package com.zhyoulun.apriori; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; public class Apriori2 { private final static int SUPPORT = 2; // 支持度閾值 private final static double CONFIDENCE = 0.7; // 置信度閾值 private final static String ITEM_SPLIT = ";"; // 項之間的分隔符 private final static String CON = "->"; // 項之間的分隔符 /** * 算法主程序 * @param dataList * @return */ public Map<String, Integer> apriori(ArrayList<String> dataList) { Map<String, Integer> stepFrequentSetMap = new HashMap<>(); stepFrequentSetMap.putAll(findFrequentOneSets(dataList)); Map<String, Integer> frequentSetMap = new HashMap<String, Integer>();//頻繁項集 frequentSetMap.putAll(stepFrequentSetMap); while(stepFrequentSetMap!=null && stepFrequentSetMap.size()>0) { Map<String, Integer> candidateSetMap = aprioriGen(stepFrequentSetMap); Set<String> candidateKeySet = candidateSetMap.keySet(); //掃描D,進行計數 for(String data:dataList) { for(String candidate:candidateKeySet) { boolean flag = true; String[] strings = candidate.split(ITEM_SPLIT); for(String string:strings) { if(data.indexOf(string+ITEM_SPLIT)==-1) { flag = false; break; } } if(flag) candidateSetMap.put(candidate, candidateSetMap.get(candidate)+1); } } //從候選集中找到符合支持度的頻繁項集 stepFrequentSetMap.clear(); for(String candidate:candidateKeySet) { Integer count = candidateSetMap.get(candidate); if(count>=SUPPORT) stepFrequentSetMap.put(candidate, count); } // 合並全部頻繁集 frequentSetMap.putAll(stepFrequentSetMap); } return frequentSetMap; } /** * find frequent 1 itemsets * @param dataList * @return */ private Map<String, Integer> findFrequentOneSets(ArrayList<String> dataList) { Map<String, Integer> resultSetMap = new HashMap<>(); for(String data:dataList) { String[] strings = data.split(ITEM_SPLIT); for(String string:strings) { string += ITEM_SPLIT; if(resultSetMap.get(string)==null) { resultSetMap.put(string, 1); } else { resultSetMap.put(string, resultSetMap.get(string)+1); } } } return resultSetMap; } /** * 依據上一步的頻繁項集的集合選出候選集 * @param setMap * @return */ private Map<String, Integer> aprioriGen(Map<String, Integer> setMap) { Map<String, Integer> candidateSetMap = new HashMap<>(); Set<String> candidateSet = setMap.keySet(); for(String s1:candidateSet) { String[] strings1 = s1.split(ITEM_SPLIT); String s1String = ""; for(String temp:strings1) s1String += temp+ITEM_SPLIT; for(String s2:candidateSet) { String[] strings2 = s2.split(ITEM_SPLIT); boolean flag = true; for(int i=0;i<strings1.length-1;i++) { if(strings1[i].compareTo(strings2[i])!=0) { flag = false; break; } } if(flag && strings1[strings1.length-1].compareTo(strings2[strings1.length-1])<0) { //連接步:產生候選 String c = s1String+strings2[strings2.length-1]+ITEM_SPLIT; if(hasInfrequentSubset(c, setMap)) { //剪枝步:刪除非頻繁的候選 } else { candidateSetMap.put(c, 0); } } } } return candidateSetMap; } /** * 使用先驗知識,推斷候選集是否是頻繁項集 * @param candidate * @param setMap * @return */ private boolean hasInfrequentSubset(String candidateSet, Map<String, Integer> setMap) { String[] strings = candidateSet.split(ITEM_SPLIT); //找出候選集全部的子集,並推斷每一個子集是否屬於頻繁子集 for(int i=0;i<strings.length;i++) { String subString = ""; for(int j=0;j<strings.length;j++) { if(j!=i) { subString += strings[j]+ITEM_SPLIT; } } if(setMap.get(subString)==null) return true; } return false; } /** * 由頻繁項集產生關聯規則 * @param frequentSetMap * @return */ public Map<String, Double> getRelationRules(Map<String, Integer> frequentSetMap) { Map<String, Double> relationsMap = new HashMap<>(); Set<String> keySet = frequentSetMap.keySet(); for(String key:keySet) { List<String> keySubset = subset(key); for(String keySubsetItem:keySubset) { //子集keySubsetItem也是頻繁項 Integer count = frequentSetMap.get(keySubsetItem); if(count!=null) { Double confidence = (1.0*frequentSetMap.get(key))/(1.0*frequentSetMap.get(keySubsetItem)); if(confidence>CONFIDENCE) relationsMap.put(keySubsetItem+CON+expect(key, keySubsetItem), confidence); } } } return relationsMap; } /** * 求一個集合全部的非空真子集 * * @param sourceSet * @return * 為了以后能夠用在其它地方。這里我們不是用遞歸的方法 * * 參考:http://blog.163.com/xiaohui_1123@126/blog/static/3980524020109784356915/ * 思路:如果集合S(A,B,C,D)。其大小為4。擁有2的4次方個子集,即0-15,二進制表示為0000,0001。...,1111。 * 相應的子集為空集。{D},...。{A,B,C,D}。 */ private List<String> subset(String sourceSet) { List<String> result = new ArrayList<>(); String[] strings = sourceSet.split(ITEM_SPLIT); //非空真子集 for(int i=1;i<(int)(Math.pow(2, strings.length))-1;i++) { String item = ""; String flag = ""; int ii=i; do { flag += ""+ii%2; ii = ii/2; } while (ii>0); for(int j=flag.length()-1;j>=0;j--) { if(flag.charAt(j)=='1') { item = strings[j]+ITEM_SPLIT+item; } } result.add(item); } return result; } /** * 集合運算,A/B * @param A * @param B * @return */ private String expect(String stringA,String stringB) { String result = ""; String[] stringAs = stringA.split(ITEM_SPLIT); String[] stringBs = stringB.split(ITEM_SPLIT); for(int i=0;i<stringAs.length;i++) { boolean flag = true; for(int j=0;j<stringBs.length;j++) { if(stringAs[i].compareTo(stringBs[j])==0) { flag = false; break; } } if(flag) result += stringAs[i]+ITEM_SPLIT; } return result; } public static void main(String[] args) { ArrayList<String> dataList = new ArrayList<>(); dataList.add("1;2;5;"); dataList.add("2;4;"); dataList.add("2;3;"); dataList.add("1;2;4;"); dataList.add("1;3;"); dataList.add("2;3;"); dataList.add("1;3;"); dataList.add("1;2;3;5;"); dataList.add("1;2;3;"); System.out.println("=數據集合=========="); for(String string:dataList) { System.out.println(string); } Apriori2 apriori2 = new Apriori2(); System.out.println("=頻繁項集=========="); Map<String, Integer> frequentSetMap = apriori2.apriori(dataList); Set<String> keySet = frequentSetMap.keySet(); for(String key:keySet) { System.out.println(key+" : "+frequentSetMap.get(key)); } System.out.println("=關聯規則=========="); Map<String, Double> relationRulesMap = apriori2.getRelationRules(frequentSetMap); Set<String> rrKeySet = relationRulesMap.keySet(); for (String rrKey : rrKeySet) { System.out.println(rrKey + " : " + relationRulesMap.get(rrKey)); } } }
計算結果
=數據集合========== 1;2;5; 2;4; 2;3; 1;2;4; 1;3; 2;3; 1;3; 1;2;3;5; 1;2;3; =頻繁項集========== 1;2; : 4 1;3; : 4 5; : 2 2;3; : 4 4; : 2 2;4; : 2 1;5; : 2 3; : 6 2; : 7 1; : 6 1;2;5; : 2 1;2;3; : 2 2;5; : 2 =關聯規則========== 4;->2; : 1.0 5;->1;2; : 1.0 5;->1; : 1.0 1;5;->2; : 1.0 5;->2; : 1.0 2;5;->1; : 1.0
參考:
http://blog.csdn.net/zjd950131/article/details/8071414
http://www.cnblogs.com/zacard-orc/p/3646979.html
數據挖掘:概念與技術
轉載請注明出處:http://blog.csdn.net/zhyoulun/article/details/41978401