SPMF源码学习与总结——Apriori算法
2015-10-02 21:35
387 查看
首先介绍下SPMF,SPMF是一个采用Java开发的开源数据挖掘平台。它提供了51种数据挖掘算法实现,用于:
•序列模式挖掘,
•关联规则挖掘,
•frequent itemset 挖掘,
•顺序规则挖掘,
•聚类
这几天放假研究了下Apriori算法的源代码,把总结写下,好记性不如一个烂笔头,防止以后忘。
Apriori算法的主要步骤:
数据读取
生成频繁1项集
如何由频繁K-1项集生成候选Ck项集(候选项集内部剪枝:确保k阶候选项集的每一个k-1阶子集都是频繁的)
计数、剪枝生成频繁k项集
现在将以上步骤的核心源代码的注释和分析贴在下面,Apriori算法的源代码在此处下载(eclipse编译过,可用eclipse直接打开使用):DataMiningApriori
(1)数据读取
根据输入文件IO路径,按行读取文件,每一行按照空格“ ”分割,分割后存入数组中,然后存入map中,key是每一个项,value是key出现的次数,即使支持度计数。通过这个操作,便可获得每一个项及其支持度,并且把数据库存入database中。输入文件为本地txt文件,内容为:
1 3 4
2 3 5
1 2 3 5
2 5
1 2 3 5
(2)如何由频繁K-1项集生成候选Ck项集
当k=2时,由频繁1项集生成候选2项集。这个情况时比较简单,直接依次比较组合即可,源代码也是这么实现的,在生成候选项集前,对候选项集进行排列,使后选项集按照字典序列排列:
(3)如何扫描数据库,计算每个候选K项集的支持度
(4) 如何判断新生成的候选Ck的k-1子集都在频繁K-1项集中
至于为什么要判断,在上面的代码的注释中已经说过,由K-1阶频繁项集合并生成的k阶候选项集的子集不一定是频繁项集,可以根据这个性质进行剪枝,所以我们根据合并产生的候选k项集,检查它的所有子集是不是都在k-1频繁项集中,若在,则说明候选项集的符合条件,若不在则应删除:
下面是sameAs()函数的源码:
•序列模式挖掘,
•关联规则挖掘,
•frequent itemset 挖掘,
•顺序规则挖掘,
•聚类
这几天放假研究了下Apriori算法的源代码,把总结写下,好记性不如一个烂笔头,防止以后忘。
Apriori算法的主要步骤:
数据读取
生成频繁1项集
如何由频繁K-1项集生成候选Ck项集(候选项集内部剪枝:确保k阶候选项集的每一个k-1阶子集都是频繁的)
计数、剪枝生成频繁k项集
现在将以上步骤的核心源代码的注释和分析贴在下面,Apriori算法的源代码在此处下载(eclipse编译过,可用eclipse直接打开使用):DataMiningApriori
(1)数据读取
根据输入文件IO路径,按行读取文件,每一行按照空格“ ”分割,分割后存入数组中,然后存入map中,key是每一个项,value是key出现的次数,即使支持度计数。通过这个操作,便可获得每一个项及其支持度,并且把数据库存入database中。输入文件为本地txt文件,内容为:
1 3 4
2 3 5
1 2 3 5
2 5
1 2 3 5
Map<Integer, Integer> mapItemCount = new HashMap<Integer, Integer>(); database = new ArrayList<int[]>(); BufferedReader reader = new BufferedReader(new FileReader(input)); String line; while (((line = reader.readLine()) != null)) { if (line.isEmpty() == true || line.charAt(0) == '#' || line.charAt(0) == '%' || line.charAt(0) == '@') { continue; } String[] lineSplited = line.split(" "); int transaction[] = new int[lineSplited.length]; for (int i=0; i< lineSplited.length; i++) { Integer item = Integer.parseInt(lineSplited[i]); transaction[i] = item; Integer count = mapItemCount.get(item); if (count == null) { mapItemCount.put(item, 1); } else { mapItemCount.put(item, ++count); } } database.add(transaction); }
(2)如何由频繁K-1项集生成候选Ck项集
当k=2时,由频繁1项集生成候选2项集。这个情况时比较简单,直接依次比较组合即可,源代码也是这么实现的,在生成候选项集前,对候选项集进行排列,使后选项集按照字典序列排列:
Collections.sort(frequent1, new Comparator<Integer>() { public int compare(Integer o1, Integer o2) { return o1 - o2; } });
//生成候选2项集,存于candidate中 private List<Itemset> generateCandidate2(List<Integer> frequent1) { List<Itemset> candidates = new ArrayList<Itemset>(); // For each itemset I1 and I2 of level k-1 for (int i = 0; i < frequent1.size(); i++) { Integer item1 = frequent1.get(i); for (int j = i + 1; j < frequent1.size(); j++) { Integer item2 = frequent1.get(j); // Create a new candidate by combining itemset1 and itemset2 candidates.add(new Itemset(new int []{item1, item2})); } } return candidates; }
当k>2时,情况比较复杂了。因为k阶候选项集的生成的前提是,前k-1阶相等,而第k阶不等,此时才能合并生成候选k项集
protected List<Itemset> generateCandidateSizeK(List<Itemset> levelK_1) { // 创建临时变量,存储k阶候选项集 List<Itemset> candidates = new ArrayList<Itemset>(); // 从候选项集取两个存于itemset1,itemset2中 loop1: for (int i = 0; i < levelK_1.size(); i++) { int[] itemset1 = levelK_1.get(i).itemset; loop2: for (int j = i + 1; j < levelK_1.size(); j++) { int[] itemset2 = levelK_1.get(j).itemset; //合并原则: 比较 itemset1 和 itemset2.如果itemset1的前k-1项与itemset2的相同,并且最后 //一项比itemset2的小,这合并itemset1、itemset2 for (int k = 0; k < itemset1.length; k++) { //当 k == itemset1.length - 1时,说明前k-1项不同 if (k == itemset1.length - 1) { //如果最后一项itemset2大,违反合并原则,终止此次比较 if (itemset1[k] >= itemset2[k]) { continue loop1; } } // 不是最后一项时,如果itemset1当前项较itemset2小,继续比较 //如果如果itemset1当前项较itemset2当前项大.由于候选项集 //是字典序列,所以不符,应终止 else if (itemset1[k] < itemset2[k]) { continue loop2; } else if (itemset1[k] > itemset2[k]) { continue loop1; } } // 符合以上条件的,合并产生K候选项集 int newItemset[] = new int[itemset1.length+1]; System.arraycopy(itemset1, 0, newItemset, 0, itemset1.length); newItemset[itemset1.length] = itemset2[itemset2.length -1]; //检查新产生的候选项集的所有子集是不是频繁项集,如果子集中有一项不是,则,立刻删除 c5af //比如频繁项集1 2和1 3合并产生1 2 3,而1 2 3的子集2 3 并不是频繁项集,所以1 2 3应删除 if (allSubsetsOfSizeK_1AreFrequent(newItemset, levelK_1)) { candidates.add(new Itemset(newItemset)); } } } return candidates; // return the set of candidates }
(3)如何扫描数据库,计算每个候选K项集的支持度
for(int[] transaction: database){ //如果当前事务的长度小于K,这终止本次扫描(这是一定的,很容易理解,如果当前事务长度小于候选项集长度,它根本就不可能包含候选项集) if(transaction.length < k) { System.out.println("test"); continue; } //接下来用从数据库中取出的事务项transaction与每个候选项依次比较, //如果包含该候选项,则该候选支持度+1 loopCand: for(Itemset candidate : candidatesK){ int pos=0; for(int item: transaction){ // 如果事务项transaction的item与candidate相对应的第pos个项相等 //则继续比较下一个 if(item == candidate.itemset[pos]){ pos++; // 当pos == candidate.itemset.length说明candidate包含在transaction中 if(pos == candidate.itemset.length){ // we increase the support of this candidate candidate.support++; continue loopCand; } //如果item > candidate.itemset[pos],即当前candidate的pos位置的项比item小, //则candidate便不可能再包含在transaction中(合并时的字典顺序决定), //比如transaction为{ 1 3 4 6}而candidate为{1 2},当pos=1时,3<2, //2便不可能存在于transaction中,所以不需要再进行比较, // 而当candidate为{1 4}时,虽然3>4,但是在3后任然可能找到和4相等的数 }else if(item > candidate.itemset[pos]){ continue loopCand; } } } }
(4) 如何判断新生成的候选Ck的k-1子集都在频繁K-1项集中
至于为什么要判断,在上面的代码的注释中已经说过,由K-1阶频繁项集合并生成的k阶候选项集的子集不一定是频繁项集,可以根据这个性质进行剪枝,所以我们根据合并产生的候选k项集,检查它的所有子集是不是都在k-1频繁项集中,若在,则说明候选项集的符合条件,若不在则应删除:
protected boolean allSubsetsOfSizeK_1AreFrequent(int[] candidate, List<Itemset> levelK_1) { // 对于某一个k阶候选项集,依次产生它的所有的k-1阶候选项集 //spfm使用了一个很让我佩服的策略是设置一个下标posRemoved, //用该标志来指明应该被忽视的项,假设候选项candidate有三个元素{1 2 3}。 //则当posRemoved=0时,第一个元素1被忽略,即产生子集{2 3}。同理依次产生子集 for(int posRemoved=0; posRemoved< candidate.length; posRemoved++){ // 由于频繁k-1阶项集也是字典排序产生,是符合大小单调性的,因此 //这里采用了折半快速查找k阶候选项集的子集在频繁k-1阶项集中的位置 int first = 0; int last = levelK_1.size() - 1; boolean found = false; while( first <= last ) { // >>1是除2的意思 int middle = ( first + last ) >>1 ; //samAs()是自定义的比较函数,比较k-1阶子集与middle所指的k-1阶频繁项集的大小 //若相等则返回为0,小于则返回1,大于则返回-1,下面的代码就是 //普通的折半查找算法,很容易理解,如果找到,就把found置true int comparison = ArraysAlgos.sameAs(levelK_1.get(middle).getItems(), candidate, posRemoved); if(comparison < 0 ){ first = middle + 1; } else if(comparison > 0 ){ last = middle - 1; } else{ found = true; break; } } if(found == false){ return false; } } return true; }
下面是sameAs()函数的源码:
//itemset1为middle指向的k-1阶频繁项集,itemsets2为k阶候选项集, //posRemoved为需要被忽略的项的下标 public static int sameAs(int [] itemset1, int [] itemsets2, int posRemoved) { int j=0; for(int i=0; i<itemset1.length; i++){ //忽略posRemoved所指的项 if(j == posRemoved){ j++; } // 依次比较项集中的每一个项的是否相等,如果相等,则继续比较 if(itemset1[i] == itemsets2[j]){ j++; }else if (itemset1[i] > itemsets2[j]){ return 1; }else{ return -1; } } return 0; }
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树
- [原创]java局域网聊天系统