【空间模式挖掘】Apriori

理论

零碎基础

  • 典例:购物篮事务——其中联系可以用关联规则或频繁项集的形式表示
  • k-项集:包含k个项。如{啤酒,尿布,牛奶}是3-项集

  • 关联规则挖掘任务,往往拆分成两个子任务:①产生频繁项集frequent itemset:满足最小支持度阈值的所有项集;②产生规则:从频繁项集中提取高置信度的规则(即强规则)

  • 频繁模式:频繁地出现在数据集中的模式(如项集、子序列或子结构)。 

  • 强规则:同时满足最小支持度阈值(min_sup)和最小置信度阈值(min_conf)的规则
  • 频繁项集挖掘的典型算法:①类Apriori算法;②基于频繁模式增长的算法,如FP-growth;③使用垂直数据格式的算法。

Apriori算法 

  • 先验原理——有助于使用支持度对候选项集剪枝。 先验(apriori)原理:若一个项集是频繁地,则它的所有子集也一定是频繁的。相反,若项集{a, b}是非频繁的,则它的所有超集也一定是非频繁的。【先验剪枝策略】【能够有效减少支持度计数中考虑的候选项集数量】
  • Apriori算法是第一个关联规则算法,它开创性地使用基于支持度的剪枝技术,系统地控制候选项集指数增长。

表6-1、6-2  Apriori算法过程示例:假定支持度阈值为60%,相当于最小支持度计数为3。

1-项集:即只包含一个项的项集

  • 初始:每个项都被看作候选1-项集。对它们的支持度计数,候选项集{可乐}和{鸡蛋}被丢弃,因其出现的事务少于3个。
  • 下次迭代:仅使用频繁1-项集产生候选2-项集。本例中,只有4个频繁1-项集,因此产生C42=6个候选2-项集——计算它们的支持度值,发现其中2个,即{啤酒,面包}和{啤酒,牛奶}是非频繁的,其余4个频繁。
  • 使用4个频繁的候选2-项集产生候选3-项集,此时,不使用基于支持度的剪枝,而是根据先验原理,只保留其子集都频繁的候选3-项集:{面包,尿布,牛奶}。

候选k项集——该操作的复杂度为O(k)

 Apriori算法示例 过程图

 Apriori算法示例 伪代码
  • Apriori算法优缺点:优点:通过使用先验原理对指数搜索空间进行剪枝,成功地处理了频繁项集产生的组合爆炸问题。 缺点:因需要多次扫描事务数据集,故算法I/O开销大。

 频繁项集的紧凑表示

  • 极大频繁项集:若该频繁项集的直接超集 都不是频繁的
  • 闭项集:若项集X的直接超集 都不具有和它相同的支持度计数【闭频繁项集:如果它是闭的,且其支持度≥最小支持度阈值】

关联模式的评估

  • 统计论据(客观兴趣度度量,如支持度、置信度和相关性) or 主观论据(模式被主观地认为是无趣的,如显而易见的规则{黄油}→{面包},除非它能揭示料想不到的信息或提供有益的行动的有益信息,如{尿布}→{啤酒}。
  • 支持度support:supp(X) =  occur(X) / count(D)  = P(X),例如:选秀投票,某选手的支持度
  • 置信度confidence:conf(X->Y) = supp(X ∪ Y) / supp(X) = P(Y|X),例如:买了A和B的人 / 已经买了 A的人的比例,这就是对A推荐B的置信度(A=>B的置信度)



实践

重要参考

(2条消息) Java实现Apriori算法进行关联规则挖掘_木子的博客-CSDN博客_apriori javahttps://blog.csdn.net/qq_24369113/article/details/53645474

Java基础

  • 运行Java之命令行 
//编译java文件→class文件
javac -encoding UTF-8 -Xlint:unchecked  AprioriMyself.java
//运行class文件
java AprioriMyself
  • 运行Java之Eclipse

 (1条消息) I/O流之进步认识、InputStream以及FileInputStream_一点一滴-CSDN博客https://blog.csdn.net/qq_34944851/article/details/52069162

程序 步骤

  • import所需
//File类: 用于描述一个文件或者文件夹,通过File对象,可以读取文件或文件夹的属性数据。如果我们需要读取文件的内容数据,那么需要使用IO流技术
//IO流:解决设备与设备之间的数据传输问题:内存—>硬盘 硬盘—>内存
import java.io.BufferedReader;//从字符输入流中读取文本,缓冲各个字符,从而提供字符,数组和行的高效读取
import java.io.File;//Java中的包就相当于windows系统中的文件夹,当前程序要使用Java文件夹下的io文件夹下的File类,由于不在同一个文件夹下的文件相互不可见,所以需要声明引入
import java.io.FileInputStream;//读取文件数据的输入字节流
import java.io.InputStreamReader;//InputStreamReader是字节流与字符流之间的桥梁,能将字节流输出为字符流,并为字节流指定字符集,输出一个个的字符
import java.util.*;//实用工具类库java.util包。在这个包中,Java提供了一些实用的方法和数据结构

  • public class AprioriMyself
public class AprioriMyself {    //AprioriMyself类, public static表示公共的静态方法,static静态变量(被所有对象共享)

    public static  int times=0;//迭代次数                  
    private static  double MIN_SUPPROT = 0.02;//最小支持度百分比
    private static   double MIN_CONFIDENCE=0.6;//最小置信度
    private static boolean endTag = false;//循环状态,迭代标识
    static List<List<String>> record = new ArrayList<List<String>>();//数据集
    static  List<List<String>> frequentItemset=new ArrayList<>();//存储所有的频繁项集
    static List<Mymap> map = new ArrayList();//存放频繁项集和对应的支持度技术

    
    
    public static void main(String args[]){ //解析:https://blog.csdn.net/qq_38807792/article/details/81010469
    	/**public:java程序的入口,java程序通过java虚拟机JVM调用,属于外部调用,所以需要使用public修饰,否则虚拟机无法调用。
    	static:没有static的变量或函数,如果想被调用的话,是要先新建一个对象才可以。而main函数作为程序的入口,需要在其它函数实例化之前就启动,这也就是为什么要加一个static。main函数好比一个门,要探索其它函数要先从门进入程序。static提供了这样一个特性,无需建立对象,就可以启动。
    	void:使用void的原因是当main方法出现返回值时JVM无法进行上抛,如果有返回值难道抛给操作系统么?
    	String args[]:args是数组的名字,并不是固定的,它是声明了一个可从控制台接受的数据的类型为String数组
 *
 */
        System.out.println("请输入最小支持度(如0.05)和最小置信度(如0.6)");//输出字符串
        Scanner in=new Scanner(System.in);//获取控制台的输入
        MIN_SUPPROT=in.nextDouble();//读取一个double类型的变量
        MIN_CONFIDENCE=in.nextDouble();


        /*************读取数据集**************/
        record = getRecord("C:\\Users\\YWP\\eclipse-workspace\\apriori2\\src\\apriori2\\top1000data");
        //控制台输出记录
        System.out.println("读取数据集record成功===================================");
        ShowData(record);


        Apriori();//调用Apriori算法获得频繁项集
        System.out.println("频繁模式挖掘完毕。\n\n\n\n\n进行关联度挖掘,最小支持度为:"+MIN_SUPPROT+"  最小置信度为:"+MIN_CONFIDENCE);
        AssociationRulesMining();//挖掘关联规则
    }
    

  • 读取数据:public static List<List<String>> getRecord(String url) 
public static List<List<String>> getRecord(String url) {
        List<List<String>> record = new ArrayList<List<String>>();
        try {
            String encoding = "UTF-8"; // 字符编码(可解决中文乱码问题 )
            File file = new File(url);//读取文件,url为路径
            if (file.isFile() && file.exists()) {
                InputStreamReader read = new InputStreamReader(
                        new FileInputStream(file), encoding);
                BufferedReader bufferedReader = new BufferedReader(read);
                String lineTXT = null;
                while ((lineTXT = bufferedReader.readLine()) != null) {//读一行文件
                    String[] lineString = lineTXT.split(",");
                    List<String> lineList = new ArrayList<String>();
                    for (int i = 0; i < lineString.length; i++) {
                        lineList.add(lineString[i]);
                    }
                    record.add(lineList);
                }

                read.close();
            } else {
                System.out.println("找不到指定的文件!");
            }
        } catch (Exception e) {
            System.out.println("读取文件内容操作出错");
            e.printStackTrace();
        }
        return record;
    }

  • 实现Apriori算法:public static void Apriori() 
public static void Apriori()           /**实现apriori算法**/
    {
        //************获取候选1项集**************
        System.out.println("第一次扫描后的1级 备选集CandidateItemset");
        List<List<String>> CandidateItemset = findFirstCandidate();
        ShowData(CandidateItemset);

        //************获取频繁1项集***************
        System.out.println("第一次扫描后的1级 频繁集FrequentItemset");
        List<List<String>> FrequentItemset = getSupprotedItemset(CandidateItemset);
        AddToFrequenceItem(FrequentItemset);//添加到所有的频繁项集中
        //控制台输出1项频繁集
        ShowData(FrequentItemset);


         //*****************************迭代过程**********************************
        times=2;
        while(endTag!=true){

            System.out.println("*******************************第"+times+"次扫描后备选集");
            //**********连接操作****获取候选times项集**************
            List<List<String>> nextCandidateItemset = getNextCandidate(FrequentItemset);
            //输出所有的候选项集
            ShowData(nextCandidateItemset);


            /**************计数操作***由候选k项集选择出频繁k项集****************/
            System.out.println("*******************************第"+times+"次扫描后频繁集");
            List<List<String>> nextFrequentItemset = getSupprotedItemset(nextCandidateItemset);
            AddToFrequenceItem(nextFrequentItemset);//添加到所有的频繁项集中
            //输出所有的频繁项集
            ShowData(nextFrequentItemset);


            //*********如果循环结束,输出最大模式**************
            if(endTag == true){
                System.out.println("\n\n\nApriori算法--->最大频繁集==================================");
                ShowData(FrequentItemset);
            }
            //****************下一次循环初值********************
            FrequentItemset = nextFrequentItemset;
            times++;//迭代次数加一
        }
    }

  • 关联规则挖掘:public static void AssociationRulesMining()
public static void AssociationRulesMining()//关联规则挖掘
    {
        for(int i=0;i<frequentItemset.size();i++)
        {
            List<String> tem=frequentItemset.get(i);
            if(tem.size()>1) {
                List<String> temclone=new ArrayList<>(tem);
                List<List<String>> AllSubset = getSubSet(temclone);//得到频繁项集tem的所有子集
                for (int j = 0; j < AllSubset.size(); j++) {
                    List<String> s1 = AllSubset.get(j);
                    List<String> s2 = gets2set(tem, s1);
                    double conf = isAssociationRules(s1, s2, tem);
                    if (conf > 0)
                        System.out.println("置信度为:" + conf);
                }
            }

            }
        }

  • 判断是否为关联规则:public  static  double isAssociationRules(List<String> s1,List<String> s2,List<String> tem)
public  static  double isAssociationRules(List<String> s1,List<String> s2,List<String> tem)//判断是否为关联规则
    {
        double confidence=0;
        int counts1;
        int countTem;
        if(s1.size()!=0&&s1!=null&&tem.size()!=0&&tem!=null)
        {
            counts1= getCount(s1);
            countTem=getCount(tem);
            confidence=countTem*1.0/counts1;

            if(confidence>=MIN_CONFIDENCE)
            {
                System.out.print("关联规则:"+ s1.toString()+"=>>"+s2.toString()+"   ");
                return confidence;
            }
            else
                return 0;

        }
        else
            return 0;

    }

  • 根据频繁项集得到 其支持度计数
    public static int getCount(List<String> in)//根据频繁项集得到 其支持度计数
    {
        int rt=0;
        for(int i=0;i<map.size();i++)
        {
            Mymap tem=map.get(i);
            if(tem.isListEqual(in)) {
                rt = tem.getcount();
                return rt;
            }
        }
        return rt;

    }

  • 计算tem减去s1后的集合即为s2:public static  List<String> gets2set(List<String> tem, List<String> s1)
    public static  List<String> gets2set(List<String> tem, List<String> s1)//计算tem减去s1后的集合即为s2
    {
        List<String> result=new ArrayList<>();

        for(int i=0;i<tem.size();i++)//去掉s1中的所有元素
        {
            String t=tem.get(i);
            if(!s1.contains(t))
                result.add(t);
        }
        return  result;
    }

    public static List<List<String>> getSubSet(List<String> set){
        List<List<String>> result = new ArrayList<>();	//用来存放子集的集合,如{{},{1},{2},{1,2}}
        int length = set.size();
        int num = length==0 ? 0 : 1<<(length);	//2的n次方,若集合set为空,num为0;若集合set有4个元素,那么num为16.

        //从0到2^n-1([00...00]到[11...11])
        for(int i = 1; i < num-1; i++){
            List<String> subSet = new ArrayList<>();

            int index = i;
            for(int j = 0; j < length; j++){
                if((index & 1) == 1){		//每次判断index最低位是否为1,为1则把集合set的第j个元素放到子集中
                    subSet.add(set.get(j));
                }
                index >>= 1;		//右移一位
            }

            result.add(subSet);		//把子集存储起来
        }
        return result;
    }

    public  static  boolean  AddToFrequenceItem(List<List<String>> fre)
    {

        for(int i=0;i<fre.size();i++)
        {
            frequentItemset.add(fre.get(i));
        }
        return true;
    }

  • 显示出candidateitem中的所有的项集:public static  void ShowData(List<List<String>> CandidateItemset)
    public static  void ShowData(List<List<String>> CandidateItemset)//显示出candidateitem中的所有的项集
    {
        for(int i=0;i<CandidateItemset.size();i++){
            List<String> list = new ArrayList<String>(CandidateItemset.get(i));
            for(int j=0;j<list.size();j++){
                System.out.print(list.get(j)+" ");
            }
            System.out.println();
        }
    }

  • 由当前频繁项集自连接求下一次候选集:private static List<List<String>> getNextCandidate(List<List<String>> FrequentItemset)
    private static List<List<String>> getNextCandidate(List<List<String>> FrequentItemset) {
        List<List<String>> nextCandidateItemset = new ArrayList<List<String>>();

        for (int i=0; i<FrequentItemset.size(); i++){
            HashSet<String> hsSet = new HashSet<String>();
            HashSet<String> hsSettemp = new HashSet<String>();
            for (int k=0; k< FrequentItemset.get(i).size(); k++)//获得频繁集第i行
                hsSet.add(FrequentItemset.get(i).get(k));
            int hsLength_before = hsSet.size();//添加前长度
            hsSettemp=(HashSet<String>) hsSet.clone();
            for(int h=i+1; h<FrequentItemset.size(); h++){//频繁集第i行与第j行(j>i)连接   每次添加且添加一个元素组成    新的频繁项集的某一行,
                hsSet=(HashSet<String>) hsSettemp.clone();//!!!做连接的hasSet保持不变
                for(int j=0; j< FrequentItemset.get(h).size();j++)
                    hsSet.add(FrequentItemset.get(h).get(j));
                int hsLength_after = hsSet.size();
                if(hsLength_before+1 == hsLength_after && isnotHave(hsSet,nextCandidateItemset)){
                    //如果不相等,表示添加了1个新的元素       同时判断其不是候选集中已经存在的一项
                    Iterator<String> itr = hsSet.iterator();
                    List<String>  tempList = new ArrayList<String>();
                    while(itr.hasNext()){
                        String Item = (String) itr.next();
                        tempList.add(Item);
                    }
                    nextCandidateItemset.add(tempList);
                }

            }

        }
        return nextCandidateItemset;
    }

  • 判断新添加元素形成的候选集是否在新的候选集中:private static boolean isnotHave(HashSet<String> hsSet, List<List<String>> nextCandidateItemset)
    private static boolean isnotHave(HashSet<String> hsSet, List<List<String>> nextCandidateItemset) {//判断hsset是不是candidateitemset中的一项
        List<String>  tempList = new ArrayList<String>();
        Iterator<String> itr = hsSet.iterator();
        while(itr.hasNext()){//将hsset转换为List<String>
            String Item = (String) itr.next();
            tempList.add(Item);
        }
        for(int i=0; i<nextCandidateItemset.size();i++)//遍历candidateitemset,看其中是否有和templist相同的一项
            if(tempList.equals(nextCandidateItemset.get(i)))
                return false;
        return true;
    }

  • 由k项候选集剪枝得到k项频繁集:private static List<List<String>> getSupprotedItemset(List<List<String>> CandidateItemset)
    private static List<List<String>> getSupprotedItemset(List<List<String>> CandidateItemset) { //对所有的商品进行支持度计数
        // TODO Auto-generated method stub
        boolean end = true;
        List<List<String>> supportedItemset = new ArrayList<List<String>>();

        for (int i = 0; i < CandidateItemset.size(); i++){

            int count = countFrequent1(CandidateItemset.get(i));//统计记录数

            if (count >= MIN_SUPPROT * (record.size()-1)){
                supportedItemset.add(CandidateItemset.get(i));
                map.add(new Mymap(CandidateItemset.get(i),count));//存储当前频繁项集以及它的支持度计数
                end = false;
            }
        }
        endTag = end;//存在频繁项集则不会结束
        if(endTag==true)
            System.out.println("*****************无满足支持度的"+times+"项集,结束连接");
        return supportedItemset;
    }

  • 统计record中出现list集合的个数:private static int countFrequent1(List<String> list)
    private static int countFrequent1(List<String> list) {//遍历所有数据集record,对单个候选集进行支持度计数

        int count =0;
        for(int i=0;i<record.size();i++)//从record的第一个开始遍历
        {
            boolean flag=true;
            for (int j=0;j<list.size();j++)//如果record中的第一个数据集包含list中的所有元素
            {
                String t=list.get(j);
                if(!record.get(i).contains(t)) {
                    flag = false;
                    break;
                }
            }
            if(flag)
                count++;//支持度加一
        }

        return count;//返回支持度计数

    }

  • 获得一项候选集:private static List<List<String>> findFirstCandidate() {
    private static List<List<String>> findFirstCandidate() {
        // TODO Auto-generated method stub
        List<List<String>> tableList = new ArrayList<List<String>>();
        HashSet<String> hs  = new HashSet<String>();//新建一个hash表,存放所有的不同的一维数据

        for (int i = 1; i<record.size(); i++){  //遍历所有的数据集,找出所有的不同的商品存放到hs中
            for(int j=1;j<record.get(i).size();j++){
                hs.add(record.get(i).get(j));
            }
        }
        Iterator<String> itr = hs.iterator();
        while(itr.hasNext()){
            List<String>  tempList = new ArrayList<String>();
            String Item = (String) itr.next();
            tempList.add(Item);   //将每一种商品存放到一个List<String>中
            tableList.add(tempList);//所有的list<String>存放到一个大的list中
        }
        return tableList;//返回所有的商品
    }
}

  • 自定义的map类,一个对象存放一个频繁项集以及其支持度计数:class  Mymap
class  Mymap{//自定义的map类,一个对象存放一个频繁项集以及其支持度计数
    public List<String> li=new LinkedList<>();
    public  int count;

    public Mymap(List<String> l,int c)//构造函数  新建一个对象
    {
        li=l;
        count=c;
    }

    public int getcount()//返回得到当前频繁项集的支持度计数
    {
        return count;
    }

    public boolean isListEqual(List<String> in)//判断传入的频繁项集是否和本频繁项集相同
    {
        if(in.size()!=li.size())//先判断大小是否相同
            return false;
        else {
            for(int i=0;i<in.size();i++)//遍历输入的频繁项集,判断是否所有元素都包含在本频繁项集中
            {
                if(!li.contains(in.get(i)))
                    return false;
            }
        }
        return true;//如果两个频繁项集大小相同,同时本频繁项集包含传入的频繁项集的所有元素,则表示两个频繁项集是相等的,返回为真
    }
}

程序 结果

  • 输出结果(几大部分)

读取数据集record成功===================================

第一次扫描后的1级 备选集CandidateItemset

第一次扫描后的1级 频繁集FrequentItemset

*******************************第2次扫描后备选集

*******************************第2次扫描后频繁集

*******************************第3次扫描后备选集

*******************************第3次扫描后频繁集

*******************************第4次扫描后备选集
*******************************第4次扫描后频繁集
*****************无满足支持度的4项集,结束连接

Apriori算法--->最大频繁集==================================
38 41 39
48 38 39
48 41 39
频繁模式挖掘完毕。


进行关联度挖掘,最小支持度百分比为:0.05  最小置信度为:0.6
关联规则:[38]=>>[39]   置信度为:0.6239669421487604
关联规则:[170]=>>[38]   置信度为:0.9821428571428571
关联规则:[41]=>>[39]   置信度为:0.7958333333333333
关联规则:[48]=>>[39]   置信度为:0.7370786516853932
关联规则:[38, 41]=>>[39]   置信度为:0.788235294117647
关联规则:[48, 38]=>>[39]   置信度为:0.7387387387387387
关联规则:[48, 41]=>>[39]   置信度为:0.835820895522388

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值