標籤:資料採礦 關聯規則 apriori演算法實現 頻繁項集
關聯規則挖掘可以發現大量資料中項集之間有趣的關聯或相關聯絡。一個典型的關聯規則挖掘例子是購物籃分析,即通過發現顧客放入其購物籃中的不同商品之間的聯絡,分析顧客的購物習慣,從而可以協助零售商指定營銷策略,引導銷售等。國外有"啤酒與尿布"的故事,國內有泡麵和火腿的故事。本文以Apriori演算法為例介紹關聯規則挖掘並以java實現。
什麼是關聯規則:
對於記錄的集合D和記錄A,記錄B,A,B屬於D: A--->B [support(A->B)=p(AUB) ,confidence(A->B)=p(B|A) ]
關聯規則的表示形式:
泡麵 ------>火腿[support=0.2,confidence=0.8]
規則的支援度和信賴度是兩個規則興趣度度量,它們分別反映發現規則的有用性和確定性。上式表示同時購買泡麵和火腿的記錄佔全部記錄的2%(實際應該沒這麼多的,不然天天吃泡麵),信賴度0.8表示在購買泡麵的記錄中,有80%的人同時購買火腿(反正我就是屬於80%的。
如果挖掘的關聯規則滿足最小支援閾值和最小信賴度閾值,則稱關聯規則是有趣的。
重要性質:頻繁項集的所有非空子集都必須是頻繁的。(一個集合如果不能通過測試,則它的所有超集也不能通過測試)
Apriori演算法思想:逐層搜尋的迭代方法,首先尋找1-項頻繁集的集合,集合記做L1, L1用於尋找兩項頻繁集合L2,L2用於尋找L3,如此下去,直到不能找K項頻繁集合。
Apriori演算法迭代的兩個階段:
1串連步;為找L(k) ,通過將L(k-1)與自身串連產生候選K項集的集合。
2減枝步;根據項的支援度計數去掉非頻繁的候選集合,確定頻繁集反覆迭代直到不能產生滿足最小支援度的集合為止。
Apriori重要性質:頻繁項集的所有非空子集都必須是頻繁的在減枝中的應用就在於,對於候選集只要它不是頻繁的,就可以刪除掉,這樣大大減少資料量。
下面直接上演算法流程圖:
下面舉例說明:
下面直接貼代碼:部分地方寫的有點冗餘,程式有點長的主要原因是向控制台輸出挖掘的過程,這樣便於理解演算法的挖掘過程
但是演算法思路是清晰的,基本上一個while就能搞定。
package cluster;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.HashSet;import java.util.Iterator;import java.util.List;/** * Apriori演算法實現 最大模式挖掘,涉及到支援度,但沒有信賴度計算 * @author push_pop * */public class AprioriMyself {private static final double MIN_SUPPROT = 0.2;//最小支援度private static boolean endTag = false;//迴圈狀態static List<List<String>> record = new ArrayList<List<String>>();//資料集public static void main(String args[]){//*************讀取資料集**************record = getRecord();//控制台輸出記錄System.out.println("以矩陣形式讀取資料集record");for(int i=0;i<record.size();i++){List<String> list= new ArrayList<String>(record.get(i));for(int j=0;j<list.size();j++){System.out.print(list.get(j)+" ");}System.out.println();}//************擷取候選1項集**************List<List<String>> CandidateItemset = findFirstCandidate();//控制台輸出1項候選集System.out.println("第一次掃描後的1級 備選集CandidateItemset");for(int i=0;i<CandidateItemset.size();i++){List<String> list = new ArrayList<String>(CandidateItemset.get(i));for(int j=0;j<list.size();j++){System.out.print(list.get(j)+" ");}System.out.println();}//************擷取頻繁1項集***************List<List<String>> FrequentItemset = getSupprotedItemset(CandidateItemset);//控制台輸出1項頻繁集System.out.println("第一次掃描後的1級 頻繁集FrequentItemset");for(int i=0;i<FrequentItemset.size();i++){List<String> list = new ArrayList<String>(FrequentItemset.get(i));for(int j=0;j<list.size();j++){System.out.print(list.get(j)+" ");}System.out.println();}//***************迭代過程**************while(endTag!=true){//**********串連操作****由k-1項頻繁集 擷取 候選k項集**************List<List<String>> nextCandidateItemset = getNextCandidate(FrequentItemset);System.out.println("掃描後備選集");for(int i=0;i<nextCandidateItemset.size();i++){List<String> list = new ArrayList<String>(nextCandidateItemset.get(i));for(int j=0;j<list.size();j++){System.out.print(list.get(j)+" ");}System.out.println();}//**************減枝操作***由候選k項集 擷取 頻繁k項集****************List<List<String>> nextFrequentItemset = getSupprotedItemset(nextCandidateItemset);System.out.println("掃描後頻繁集");for(int i=0;i<nextFrequentItemset.size();i++){List<String> list = new ArrayList<String>(nextFrequentItemset.get(i));for(int j=0;j<list.size();j++){System.out.print(list.get(j)+" ");}System.out.println();}//*********如果迴圈結束,輸出最大模式**************if(endTag == true){System.out.println("Apriori演算法--->頻繁集");for(int i=0;i<FrequentItemset.size();i++){List<String> list = new ArrayList<String>(FrequentItemset.get(i));for(int j=0;j<list.size();j++){System.out.print(list.get(j)+" ");}System.out.println();}}//****************下一次迴圈初值********************CandidateItemset = nextCandidateItemset;FrequentItemset = nextFrequentItemset;}}/** * 讀取txt資料 * @return */public static List<List<String>> getRecord() {List<List<String>> record = new ArrayList<List<String>>();try {String encoding = "GBK"; // 字元編碼(可解決中文亂碼問題 )File file = new File("simple.txt");if (file.isFile() && file.exists()) {InputStreamReader read = new InputStreamReader(new FileInputStream(file), encoding);BufferedReader bufferedReader = new BufferedReader(read);String lineTXT = null;while ((lineTXT = bufferedReader.readLine()) != null) {//讀一行檔案String[] lineString = lineTXT.split("");List<String> lineList = new ArrayList<String>();for (int i = 0; i < lineString.length; i++) {//處理矩陣中的T、F、YES、NOif (lineString[i].endsWith("T")|| lineString[i].endsWith("YES"))lineList.add(record.get(0).get(i));else if (lineString[i].endsWith("F")|| lineString[i].endsWith("NO"));// F,NO記錄不儲存elselineList.add(lineString[i]);}record.add(lineList);}read.close();} else {System.out.println("找不到指定的檔案!");}} catch (Exception e) {System.out.println("讀取檔案內容操作出錯");e.printStackTrace();}return record;}/** * 有當前頻繁項集自串連求下一次候選集 * @param FrequentItemset * @return */private static List<List<String>> getNextCandidate(List<List<String>> FrequentItemset) {List<List<String>> nextCandidateItemset = new ArrayList<List<String>>();for (int i=0; i<FrequentItemset.size(); i++){HashSet<String> hsSet = new HashSet<String>();HashSet<String> hsSettemp = new HashSet<String>();for (int k=0; k< FrequentItemset.get(i).size(); k++)//獲得頻繁集第i行hsSet.add(FrequentItemset.get(i).get(k));int hsLength_before = hsSet.size();//添加前長度hsSettemp=(HashSet<String>) hsSet.clone();for(int h=i+1; h<FrequentItemset.size(); h++){//頻繁集第i行與第j行(j>i)串連 每次添加且添加一個元素組成 新的頻繁項集的某一行, hsSet=(HashSet<String>) hsSettemp.clone();//!!!做串連的hasSet保持不變for(int j=0; j< FrequentItemset.get(h).size();j++)hsSet.add(FrequentItemset.get(h).get(j));int hsLength_after = hsSet.size();if(hsLength_before+1 == hsLength_after && isSubsetOf(hsSet,record)==1 && isnotHave(hsSet,nextCandidateItemset)){//如果不相等,表示添加了1個新的元素,再判斷其是否為record某一行的子集 若是則其為 候選集中的一項Iterator<String> itr = hsSet.iterator();List<String> tempList = new ArrayList<String>();while(itr.hasNext()){String Item = (String) itr.next();tempList.add(Item);}nextCandidateItemset.add(tempList);}}}return nextCandidateItemset;}/** * 判斷新添加元素形成的候選集是否在 新的候選集中 * @param hsSet * @param nextCandidateItemset * @return */private static boolean isnotHave(HashSet<String> hsSet,List<List<String>> nextCandidateItemset) {// TODO Auto-generated method stubList<String> tempList = new ArrayList<String>();Iterator<String> itr = hsSet.iterator();while(itr.hasNext()){String Item = (String) itr.next();tempList.add(Item);}for(int i=0; i<nextCandidateItemset.size();i++)if(tempList.equals(nextCandidateItemset.get(i)))return false;return true;}/** * 判斷hsSet是不是record2中的某一記錄子集 * @param hsSet * @param record2 * @return */private static int isSubsetOf(HashSet<String> hsSet,List<List<String>> record2) {//hsSet轉換成ListList<String> tempList = new ArrayList<String>();Iterator<String> itr = hsSet.iterator();while(itr.hasNext()){String Item = (String) itr.next();tempList.add(Item);}for(int i=1;i<record.size();i++){List<String> tempListRecord = new ArrayList<String>();for(int j=1;j<record.get(i).size();j++)tempListRecord.add(record.get(i).get(j));if(tempListRecord.containsAll(tempList))return 1;}return 0;}/** * 由k項候選集剪枝得到k項頻繁集 * @param CandidateItemset * @return */private static List<List<String>> getSupprotedItemset(List<List<String>> CandidateItemset) {// TODO Auto-generated method stubboolean end = true;List<List<String>> supportedItemset = new ArrayList<List<String>>();int k = 0;for (int i = 0; i < CandidateItemset.size(); i++){int count = countFrequent(CandidateItemset.get(i));//統計記錄數if (count >= MIN_SUPPROT * (record.size()-1)){supportedItemset.add(CandidateItemset.get(i));end = false;}}endTag = end;//存在頻繁項集則不會結束if(endTag==true)System.out.println("無滿足支援度項集,結束串連");return supportedItemset;}/** * 統計record中出現list集合的個數 * @param list * @return */private static int countFrequent(List<String> list) {// TODO Auto-generated method stubint count = 0;for(int i = 1; i<record.size(); i++) {boolean notHaveThisList = false;for (int k=0; k < list.size(); k++){//判斷record.get(i)是否包含listboolean thisRecordHave = false;for(int j=1; j<record.get(i).size(); j++){if(list.get(k).equals(record.get(i).get(j)))//list。get(k)在record。get(i)中能找到thisRecordHave = true;}if(!thisRecordHave){//只要有一個list元素找不到,則退出其餘元素比較,進行下一個record。get(i)比較notHaveThisList = true;break;}}if(notHaveThisList == false)count++;}return count;}/** * 獲得一項候選集 * @return */private static List<List<String>> findFirstCandidate() {// TODO Auto-generated method stubList<List<String>> tableList = new ArrayList<List<String>>();HashSet<String> hs = new HashSet<String>();for (int i = 1; i<record.size(); i++){ //第一行為商品資訊for(int j=1;j<record.get(i).size();j++){hs.add(record.get(i).get(j));}}Iterator<String> itr = hs.iterator();while(itr.hasNext()){List<String> tempList = new ArrayList<String>();String Item = (String) itr.next();tempList.add(Item);tableList.add(tempList);}return tableList;}}
Apriori演算法的缺陷也是很明顯的:
1 .若資料量較大,將大量的候選集。N個頻繁1項集可能產生(N-1)*N/2個候選2項集
2 資料庫需要多邊掃描,頻繁集每自串連一次,就要重新掃面一次資料。
關於其改進將在下一篇部落格寫出--不產生候選集的關聯規則挖掘演算法FPTree
Apriori演算法學習和java實現