SPMF源码学习与总结——Apriori算法

首先介绍下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

        Map mapItemCount = new HashMap();       
        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() {
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
//生成候选2项集,存于candidate中
private List generateCandidate2(List frequent1) {
        List candidates = new ArrayList();
        // 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 generateCandidateSizeK(List levelK_1) {
        // 创建临时变量,存储k阶候选项集
        List candidates = new ArrayList();
        // 从候选项集取两个存于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];

                //检查新产生的候选项集的所有子集是不是频繁项集,如果子集中有一项不是,则,立刻删除
                //比如频繁项集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 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//忽略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,数据挖掘,Apriori,源代码,SPMF)