(update 2012.12.28 关于本项目下载及运行的常见问题 FAQ见 newsgroup18828文本分类器、文本聚类器、关联分析频繁模式挖掘算法的Java实现工程下载及运行FAQ )
一、Apriori算法
Apriori是非常经典的关联分析频繁模式挖掘算法,其思想简明,实现方便,只是效率很低,可以作为频繁模式挖掘的入门算法。其主要特点是
package com.pku.yangliu; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /**频繁模式挖掘算法Apriori实现 * */ public class AprioriFPMining { private int minSup;//最小支持度 private static List<Set<String>> dataTrans;//以List<Set<String>>格式保存的事物数据库,利用Set的有序性 public int getMinSup() { return minSup; } public void setMinSup(int minSup) { this.minSup = minSup; } /** * @param args */ public static void main(String[] args) throws IOException { AprioriFPMining apriori = new AprioriFPMining(); double [] threshold = {0.25, 0.20, 0.15, 0.10, 0.05}; String srcFile = "F:/DataMiningSample/FPmining/Mushroom.dat"; String shortFileName = srcFile.split("/")[3]; String targetFile = "F:/DataMiningSample/FPmining/" + shortFileName.substring(0, shortFileName.indexOf("."))+"_fp_threshold"; dataTrans = apriori.readTrans(srcFile); for(int k = 0; k < threshold.length; k++){ System.out.println(srcFile + " threshold: " + threshold[k]); long totalItem = 0; long totalTime = 0; FileWriter tgFileWriter = new FileWriter(targetFile + (threshold[k]*100)); apriori.setMinSup((int)(dataTrans.size() * threshold[k]));//原始蘑菇的数据0.25只需要67秒跑出结果 long startTime = System.currentTimeMillis(); Map<String, Integer> f1Set = apriori.findFP1Items(dataTrans); long endTime = System.currentTimeMillis(); totalTime += endTime - startTime; //频繁1项集信息得加入支持度 Map<Set<String>, Integer> f1Map = new HashMap<Set<String>, Integer>(); for(Map.Entry<String, Integer> f1Item : f1Set.entrySet()){ Set<String> fs = new HashSet<String>(); fs.add(f1Item.getKey()); f1Map.put(fs, f1Item.getValue()); } totalItem += apriori.printMap(f1Map, tgFileWriter); Map<Set<String>, Integer> result = f1Map; do { startTime = System.currentTimeMillis(); result = apriori.genNextKItem(result); endTime = System.currentTimeMillis(); totalTime += endTime - startTime; totalItem += apriori.printMap(result, tgFileWriter); } while(result.size() != 0); tgFileWriter.close(); System.out.println("共用时:" + totalTime + "ms"); System.out.println("共有" + totalItem + "项频繁模式"); } } /**由频繁K-1项集生成频繁K项集 * @param preMap 保存频繁K项集的map * @param tgFileWriter 输出文件句柄 * @return int 频繁i项集的数目 * @throws IOException */ private Map<Set<String>, Integer> genNextKItem(Map<Set<String>, Integer> preMap) { // TODO Auto-generated method stub Map<Set<String>, Integer> result = new HashMap<Set<String>, Integer>(); //遍历两个k-1项集生成k项集 List<Set<String>> preSetArray = new ArrayList<Set<String>>(); for(Map.Entry<Set<String>, Integer> preMapItem : preMap.entrySet()){ preSetArray.add(preMapItem.getKey()); } int preSetLength = preSetArray.size(); for (int i = 0; i < preSetLength - 1; i++) { for (int j = i + 1; j < preSetLength; j++) { String[] strA1 = preSetArray.get(i).toArray(new String[0]); String[] strA2 = preSetArray.get(j).toArray(new String[0]); if (isCanLink(strA1, strA2)) { // 判断两个k-1项集是否符合连接成k项集的条件 Set<String> set = new TreeSet<String>(); for (String str : strA1) { set.add(str); } set.add((String) strA2[strA2.length - 1]); // 连接成k项集 // 判断k项集是否需要剪切掉,如果不需要被cut掉,则加入到k项集列表中 if (!isNeedCut(preMap, set)) {//由于单调性,必须保证k项集的所有k-1项子集都在preMap中出现,否则就该剪切该k项集 result.put(set, 0); } } } } return assertFP(result);//遍历事物数据库,求支持度,确保为频繁项集 } /**检测k项集是否该剪切。由于单调性,必须保证k项集的所有k-1项子集都在preMap中出现,否则就该剪切该k项集 * @param preMap k-1项频繁集map * @param set 待检测的k项集 * @return boolean 是否该剪切 * @throws IOException */ private boolean isNeedCut(Map<Set<String>, Integer> preMap, Set<String> set) { // TODO Auto-generated method stub boolean flag = false; List<Set<String>> subSets = getSubSets(set); for(Set<String> subSet : subSets){ if(!preMap.containsKey(subSet)){ flag = true; break; } } return flag; } /**获取k项集set的所有k-1项子集 * @param set 频繁k项集 * @return List<Set<String>> 所有k-1项子集容器 * @throws IOException */ private List<Set<String>> getSubSets(Set<String> set) { // TODO Auto-generated method stub String[] setArray = set.toArray(new String[0]); List<Set<String>> result = new ArrayList<Set<String>>(); for(int i = 0; i < setArray.length; i++){ Set<String> subSet = new HashSet<String>(); for(int j = 0; j < setArray.length; j++){ if(j != i) subSet.add(setArray[j]); } result.add(subSet); } return result; } /**遍历事物数据库,求支持度,确保为频繁项集 * @param allKItem 候选频繁k项集 * @return Map<Set<String>, Integer> 支持度大于阈值的频繁项集和支持度map * @throws IOException */ private Map<Set<String>, Integer> assertFP( Map<Set<String>, Integer> allKItem) { // TODO Auto-generated method stub Map<Set<String>, Integer> result = new HashMap<Set<String>, Integer>(); for(Set<String> kItem : allKItem.keySet()){ for(Set<String> data : dataTrans){ boolean flag = true; for(String str : kItem){ if(!data.contains(str)){ flag = false; break; } } if(flag) allKItem.put(kItem, allKItem.get(kItem) + 1); } if(allKItem.get(kItem) >= minSup) { result.put(kItem, allKItem.get(kItem)); } } return result; } /**检测两个频繁K项集是否可以连接,连接条件是只有最后一个项不同 * @param strA1 k项集1 * @param strA1 k项集2 * @return boolean 是否可以连接 * @throws IOException */ private boolean isCanLink(String[] strA1, String[] strA2) { // TODO Auto-generated method stub boolean flag = true; if(strA1.length != strA2.length){ return false; }else { for(int i = 0; i < strA1.length - 1; i++){ if(!strA1[i].equals(strA2[i])){ flag = false; break; } } if(strA1[strA1.length -1].equals(strA2[strA1.length -1])){ flag = false; } } return flag; } /**将频繁i项集的内容及支持度输出到文件 格式为 模式:支持度 * @param f1Map 保存频繁i项集的容器<i项集 , 支持度> * @param tgFileWriter 输出文件句柄 * @return int 频繁i项集的数目 * @throws IOException */ private int printMap(Map<Set<String>, Integer> f1Map, FileWriter tgFileWriter) throws IOException { // TODO Auto-generated method stub for(Map.Entry<Set<String>, Integer> f1MapItem : f1Map.entrySet()){ for(String p : f1MapItem.getKey()){ tgFileWriter.append(p + " "); } tgFileWriter.append(": " + f1MapItem.getValue() + "\n"); } tgFileWriter.flush(); return f1Map.size(); } /**生成频繁1项集 * @param fileDir 事务文件目录 * @return Map<String, Integer> 保存频繁1项集的容器<1项集 , 支持度> * @throws IOException */ private Map<String, Integer> findFP1Items(List<Set<String>> dataTrans) { // TODO Auto-generated method stub Map<String, Integer> result = new HashMap<String, Integer>(); Map<String, Integer> itemCount = new HashMap<String, Integer>(); for(Set<String> ds : dataTrans){ for(String d : ds){ if(itemCount.containsKey(d)){ itemCount.put(d, itemCount.get(d) + 1); } else { itemCount.put(d, 1); } } } for(Map.Entry<String, Integer> ic : itemCount.entrySet()){ if(ic.getValue() >= minSup){ result.put(ic.getKey(), ic.getValue()); } } return result; } /**读取事务数据库 * @param fileDir 事务文件目录 * @return List<String> 保存事务的容器 * @throws IOException */ private List<Set<String>> readTrans(String fileDir) { // TODO Auto-generated method stub List<Set<String>> records = new ArrayList<Set<String>>(); try { FileReader fr = new FileReader(new File(fileDir)); BufferedReader br = new BufferedReader(fr); String line = null; while ((line = br.readLine()) != null) { if (line.trim() != "") { Set<String> record = new HashSet<String>(); String[] items = line.split(" "); for (String item : items) { record.add(item); } records.add(record); } } } catch (IOException e) { System.out.println("读取事务文件失败。"); System.exit(-2); } return records; } }硬件环境:Intel Core 2 Duo CPU T5750 2GHZ, 2G内存
package com.pku.yhf; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.BitSet; import java.util.Iterator; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; public class EclatRelease { private File file=new File("D:/mushroom.dat.txt"); private float limitValue=0.25f; private int transNum=0; private ArrayList<HeadNode> array=new ArrayList<HeadNode>(); private HashHeadNode[] hashTable;//存放临时生成的频繁项集,作为重复查询的备选集合 public long newItemNum=0; private File tempFile=null; private BufferedWriter bw=null; public static long modSum=0; /** * 第一遍扫描数据库,确定Itemset,根据阈值计算出支持度数 */ public void init() { Set itemSet=new TreeSet(); MyMap<Integer,Integer> itemMap=new MyMap<Integer,Integer>(); int itemNum=0; Set[][] a; try { FileInputStream fis=new FileInputStream(file); BufferedReader br=new BufferedReader(new InputStreamReader(fis)); String str=null; //第一次扫描数据集合 while((str=br.readLine()) != null) { transNum++; String[] line=str.split(" "); for(String item:line) { itemSet.add(Integer.parseInt(item)); itemMap.add(Integer.parseInt((item))); } } br.close(); // System.out.println("itemMap lastKey:"+itemMap.lastKey()); // System.out.println("itemsize:"+itemSet.size()); // System.out.println("trans: "+transNum); //ItemSet.limitSupport=(int)Math.ceil(transNum*limitValue);//上取整 ItemSet.limitSupport=(int)Math.floor(transNum*limitValue);//下取整 ItemSet.ItemSize=(Integer)itemMap.lastKey(); ItemSet.TransSize=transNum; hashTable=new HashHeadNode[ItemSet.ItemSize*3];//生成项集hash表 for(int i=0;i<hashTable.length;i++) { hashTable[i]=new HashHeadNode(); } // System.out.println("limitSupport:"+ItemSet.limitSupport); tempFile=new File(file.getParent()+"/"+file.getName()+".dat"); if(tempFile.exists()) { tempFile.delete(); } tempFile.createNewFile(); bw=new BufferedWriter(new FileWriter(tempFile)); Set oneItem=itemMap.keySet(); int countOneItem=0; for(Iterator it=oneItem.iterator();it.hasNext();) { int key=(Integer)it.next(); int value=(Integer)itemMap.get(key); if(value >= ItemSet.limitSupport) { bw.write(key+" "+":"+" "+value); bw.write("\n"); countOneItem++; } } bw.flush(); modSum+=countOneItem; itemNum=(Integer)itemMap.lastKey(); a=new TreeSet[itemNum+1][itemNum+1]; array.add(new HeadNode());//空项 for(short i=1;i<=itemNum;i++) { HeadNode hn=new HeadNode(); // hn.item=i; array.add(hn); } BufferedReader br2=new BufferedReader(new FileReader(file)); //第二次扫描数据集合,形成2-项候选集 int counter=0;//事务 int max=0; while((str=br2.readLine()) != null) {max++; String[] line=str.split(" "); counter++; for(int i=0;i<line.length;i++) { int sOne=Integer.parseInt(line[i]); for(int j=i+1;j<line.length;j++) { int sTwo=Integer.parseInt(line[j]); if(a[sOne][sTwo] == null) { Set set=new TreeSet(); set.add(counter); a[sOne][sTwo]=set; } else{ a[sOne][sTwo].add(counter); } } } } //将数组集合转换为链表集合 for(int i=1;i<=itemNum;i++) { HeadNode hn=array.get(i); for(int j=i+1;j<=itemNum;j++) { if(a[i][j] != null && a[i][j].size() >= ItemSet.limitSupport) { hn.items++; ItemSet is=new ItemSet(true); is.item=2; is.items.set(i); is.items.set(j); is.supports=a[i][j].size(); bw.write(i+" "+j+" "+": "+is.supports); bw.write("\n"); //统计频繁2-项集的个数 modSum++; for(Iterator it=a[i][j].iterator();it.hasNext();) { int value=(Integer)it.next(); is.trans.set(value); } if( hn.first== null) { hn.first=is; hn.last=is; } else{ hn.last.next=is; hn.last=is; } } } } bw.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void start() { boolean flag=true; //TreeSet ts=new TreeSet();//临时存储项目集合,防止重复项集出现,节省空间 int count=0; ItemSet shareFirst=new ItemSet(false); while(flag) { flag=false; //System.out.println(++count); for(int i=1;i<array.size();i++) { HeadNode hn=array.get(i); if(hn.items > 1 )//项集个数大于1 { generateLargeItemSet(hn,shareFirst); flag=true; } clear(hashTable); } }try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } public void generateLargeItemSet(HeadNode hn,ItemSet shareFirst){ BitSet bsItems=new BitSet(ItemSet.ItemSize);//存放链两个k-1频繁项集的ItemSet交 BitSet bsTrans=new BitSet(ItemSet.TransSize);//存放两个k-1频繁项集的Trans交 BitSet containItems=new BitSet(ItemSet.ItemSize);//存放两个k-1频繁项集的ItemSet的并 BitSet bsItems2=new BitSet(ItemSet.ItemSize);//临时存放容器BitSet ItemSet oldCurrent=null,oldNext=null; oldCurrent=hn.first; long countItems=0; ItemSet newFirst=new ItemSet(false),newLast=newFirst; while(oldCurrent != null) { oldNext=oldCurrent.next; while(oldNext != null) { //生成k—项候选集,由两个k-1项频繁集生成 bsItems.clear(); bsItems.or(oldCurrent.items); bsItems.and(oldNext.items); if(bsItems.cardinality() < oldCurrent.item-1) { break; } //新合并的项集是否已经存在 containItems.clear(); containItems.or(oldCurrent.items);//将k-1项集合并 containItems.or(oldNext.items); if(!containItems(containItems,bsItems2,newFirst)){ bsTrans.clear(); bsTrans.or(oldCurrent.trans); bsTrans.and(oldNext.trans); if(bsTrans.cardinality() >= ItemSet.limitSupport) { ItemSet is=null; if(shareFirst.next == null)//没有共享ItemSet链表 { is=new ItemSet(true); newItemNum++; } else { is=shareFirst.next; shareFirst.next=shareFirst.next.next; is.items.clear(); is.trans.clear(); is.next=null; } is.item=(oldCurrent.item+1);//生成k—项候选集,由两个k-1项频繁集生成 is.items.or(oldCurrent.items);//将k-1项集合并 is.items.or(oldNext.items);//将k-1项集合并 is.trans.or(oldCurrent.trans);//将bs1的值复制到bs中 is.trans.and(oldNext.trans); is.supports=is.trans.cardinality(); writeToFile(is.items,is.supports);//将频繁项集及其支持度写入文件 countItems++; modSum++; newLast.next=is; newLast=is; } } oldNext=oldNext.next; } oldCurrent=oldCurrent.next; } ItemSet temp1=hn.first; ItemSet temp2=hn.last; temp2.next=shareFirst.next; shareFirst.next=temp1; hn.first=newFirst.next; hn.last=newLast; hn.items=countItems; } public boolean containItems(BitSet containItems,BitSet bsItems2,ItemSet first) { long size=containItems.cardinality();//项集数目 int itemSum=0; int temp=containItems.nextSetBit(0); while(true) { itemSum+=temp; temp=containItems.nextSetBit(temp+1); if(temp == -1) { break; } } int hash=itemSum%(ItemSet.ItemSize*3); HashNode hn=hashTable[hash].next; Node pre=hashTable[hash]; while(true) { if(hn == null)//不包含containItems { HashNode node=new HashNode(); node.bs.or(containItems); pre.next=node; return false; } if(hn.bs.isEmpty()) { hn.bs.or(containItems); return false; } bsItems2.clear(); bsItems2.or(containItems); bsItems2.and(hn.bs); if(bsItems2.cardinality() == size) { return true; } pre=hn; hn=hn.next; } } public void clear(HashHeadNode[] hashTable) { for(int i=0;i<hashTable.length;i++) { HashNode node=hashTable[i].next; while(node != null) { node.bs.clear(); node=node.next; } } } public void writeToFile(BitSet items,int supports) { StringBuilder sb=new StringBuilder(); //sb.append("<"); int temp=items.nextSetBit(0); sb.append(temp); while(true) { temp=items.nextSetBit(temp+1); if(temp == -1) { break; } //sb.append(","); sb.append(" "); sb.append(temp); } sb.append(" :"+" "+supports); try { bw.write(sb.toString()); bw.write("\n"); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { EclatRelease e=new EclatRelease(); long begin=System.currentTimeMillis(); e.init(); e.start(); long end=System.currentTimeMillis(); double time=(double)(end-begin)/1000; System.out.println("共耗时"+time+"秒"); System.out.println("频繁模式数目:"+EclatRelease.modSum); } } class MyMap<T,E> extends TreeMap { public void add(T obj) { if(this.containsKey(obj)) { int value=(Integer)this.get(obj); this.put(obj, value+1); } else this.put(obj, 1); } }ItemSet类如下
package com.pku.yhf; import java.util.BitSet; public class ItemSet { public static int limitSupport;//根据阈值计算出的最小支持度数 public static int ItemSize;//Items数目 public static int TransSize; //事务数目 public boolean flag=true; //true,表示作为真正的ItemSet,false只作为标记节点,只在HashTabel中使用 public int item=0;// 某项集 public int supports=0;//项集的支持度 public BitSet items=null; public BitSet trans=null; //public TreeSet items=new TreeSet();//项集 //public TreeSet trans=new TreeSet();//事务集合 public ItemSet next=null;//下一个项集 public ItemSet(boolean flag) { this.flag=flag; if(flag) { item=0;// 某项集 supports=0;//项集的支持度 items=new BitSet(ItemSize+1); trans=new BitSet(TransSize+1); } } }