注:本文轉載自yangliuy http://blog.csdn.net/yangliuy/article/details/7494983 數據挖掘-關聯分析頻繁模式挖掘Apriori、FP-Growth及Eclat算法的JAVA及C++實現
一、Apriori算法
Apriori是非常經典的關聯分析頻繁模式挖掘算法,其思想簡明,實現方便,只是效率很低,可以作爲頻繁模式挖掘的入門算法。其主要特點是
2. 從2項集開始循環,由頻繁k-1項集生成頻繁頻繁k項集。
2.1 頻繁k-1項集兩兩組合,判定是否可以連接,若能則連接生成k項集。
2.2 對k項集中的每個項集檢測其子集是否頻繁,捨棄掉子集不是頻繁項集即 不在頻繁k-1項集中的項集。
2.3 掃描數據庫,計算2.3步中過濾後的k項集的支持度,捨棄掉支持度小於閾值的項集,生成頻繁k項集。
3. 若當前k項集中只有一個項集時循環結束。
僞代碼如下:
JAVA實現代碼
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;
}
}
實驗結果
F:/DataMiningSample/FPmining/Mushroom.dat threshold: 0.25
共用時:54015ms
共有5545項頻繁模式
F:/DataMiningSample/FPmining/Mushroom.dat threshold: 0.2
共用時:991610ms
共有53663項頻繁模式
F:/DataMiningSample/FPmining/Mushroom.dat threshold: 0.15
解決辦法:改用C++寫FP-Growth算法做頻繁模式挖掘!
Step2 遍歷FP-tree的頭表,對於每個頻繁項x,累積項x的所有前綴路徑形成x的條件模式庫CPB
Step3
對CPB上每一條路徑的節點更新計數爲x的計數,根據CPB構造條件FP-tree
Step4 從條件FP-tree中找到所有長路徑,對該路徑上的節點找出所有組合方式,然後合併計數
Step5 將Step4中的頻繁項集與x合併,得到包含x的頻繁項集
Step2-5 循環,直到遍歷頭表中的所有項
由於時間關係,主要基於芬蘭教授Bart Goethals的開源代碼實現,源碼下載見點擊打開鏈接 ,文件結構及運行結果如下
對Mushroom.dat,accidents.dat和T10I4D100K.dat三個數據集做頻繁模式挖掘的結果如下
Eclat算法加入了倒排的思想,加快頻繁集生成速度,其算法思想是 由頻繁k項集求交集,生成候選k+1項集 。對候選k+1項集做裁剪,生成頻繁k+1項集,再求交集生成候選k+2項集。如此迭代,直到項集歸一。
算法過程:
1.一次掃描數據庫,獲得初始數據。包括頻繁1項集,數據庫包含的所有items,事務總數(行)transNum,最小支持度minsup=limitValue*trans。
2.二次掃描數據庫,獲得頻繁2項集。
3.按照Eclat算法,對頻繁2項集迭代求交集,做裁剪,直到項集歸一。
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);
}
}
}
對mushroom.dat的頻繁模式挖掘結果如下