(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> dataTrans;//以List>格式保存的事物数据库,利用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 f1Set = apriori.findFP1Items(dataTrans);
long endTime = System.currentTimeMillis();
totalTime += endTime - startTime;
//频繁1项集信息得加入支持度
Map, Integer> f1Map = new HashMap, Integer>();
for(Map.Entry f1Item : f1Set.entrySet()){
Set fs = new HashSet();
fs.add(f1Item.getKey());
f1Map.put(fs, f1Item.getValue());
}
totalItem += apriori.printMap(f1Map, tgFileWriter);
Map, 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, Integer> genNextKItem(Map, Integer> preMap) {
// TODO Auto-generated method stub
Map, Integer> result = new HashMap, Integer>();
//遍历两个k-1项集生成k项集
List> preSetArray = new ArrayList>();
for(Map.Entry, 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 set = new TreeSet();
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, Integer> preMap, Set set) {
// TODO Auto-generated method stub
boolean flag = false;
List> subSets = getSubSets(set);
for(Set subSet : subSets){
if(!preMap.containsKey(subSet)){
flag = true;
break;
}
}
return flag;
}
/**获取k项集set的所有k-1项子集
* @param set 频繁k项集
* @return List> 所有k-1项子集容器
* @throws IOException
*/
private List> getSubSets(Set set) {
// TODO Auto-generated method stub
String[] setArray = set.toArray(new String[0]);
List> result = new ArrayList>();
for(int i = 0; i < setArray.length; i++){
Set subSet = new HashSet();
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, Integer> 支持度大于阈值的频繁项集和支持度map
* @throws IOException
*/
private Map, Integer> assertFP(
Map, Integer> allKItem) {
// TODO Auto-generated method stub
Map, Integer> result = new HashMap, Integer>();
for(Set kItem : allKItem.keySet()){
for(Set 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项集的容器
* @param tgFileWriter 输出文件句柄
* @return int 频繁i项集的数目
* @throws IOException
*/
private int printMap(Map, Integer> f1Map, FileWriter tgFileWriter) throws IOException {
// TODO Auto-generated method stub
for(Map.Entry, 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 保存频繁1项集的容器<1项集 , 支持度>
* @throws IOException
*/
private Map findFP1Items(List> dataTrans) {
// TODO Auto-generated method stub
Map result = new HashMap();
Map itemCount = new HashMap();
for(Set 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 ic : itemCount.entrySet()){
if(ic.getValue() >= minSup){
result.put(ic.getKey(), ic.getValue());
}
}
return result;
}
/**读取事务数据库
* @param fileDir 事务文件目录
* @return List 保存事务的容器
* @throws IOException
*/
private List> readTrans(String fileDir) {
// TODO Auto-generated method stub
List> records = new ArrayList>();
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 record = new HashSet();
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 array=new ArrayList();
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 itemMap=new MyMap();
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= 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= 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 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 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);
}
}
}