JavaApriori算法

Posted yongh701

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaApriori算法相关的知识,希望对你有一定的参考价值。

Apriori算法是一种最基本的根据事务记录求解关联的算法。在1994年已经由Agrawal和Srikant提出。也就是那个成功在一大堆顾客的购买记录中,求解出买完啤酒还要买尿布的结论的著名数据挖掘算法。现在关联算法已经发展得多种多样,无数学术论文的产生,让关联求解更加多样化,但都是依据这个Apriori作进一步发展。

利用Java完成Apriori算法其实很简单,在贴代码之前,首先讲解Apriori算法的基本思想。假如有如下的事务记录:


也就是说,现在有4个人,一个买了I1 I2 I3 I4商品,或者到了我们店里面享受了I1 I2 I3 I4服务,或者在某一时间段,做了I1 I2 I3 I4之类的,然后又有一个人,买了I1 I2 I3商品……以此类推,我们现在要根据所有用户的购买/服务/事件记录,也就是所谓的事务数据库D,求解某个人,做完某事之后很可能做什么?

Apriori算法就是做这件事,其根据一个事件序列在整个事务数据库D出现的概率多少,判断大家是否很可能同时会做这个事件序列。然后再从这个事件序列的子集当中,推断之间的关联性,具体如下:

一、对项目集C和候选集L进行初始化

初始化过程是求解各个独立事件的支持度support。所谓的支持度support就是 该独立事件在一条事件数出现的次数 除以 事务数据库D事务的总数。

比如I1事件,他在第1、2、4条都出现了,共3次,而D中的事务总数共4条,那么,其支持度则为3/4,75%。所谓的“出现”也就是,I1是否为该事件序列的子集。

初始化后,项目集C如下:


这个C可以在Java中,用HashMap<ArrayList<String>, Double> C表示,这里的key是ArrayList<String>而不是String,是因为后续的事件还要进一步扩大,变成I1 I2,这是后话了。

之后我们要设计一个最小支持度min_support,这里定为0.5,用于淘汰在事务数据中出现过少的事件,比如I5。因为这事做得人少,肯定代表大家是不怎么愿意做他,也不会成为关联事件序列的候选事件了。

在项目集C淘汰支持度过少的事件之后,形成候选集L,如下:


这也是所谓的“剪枝步”

二、迭代求最终的项目集C和候选集L

上述的C与L并不足以支持我们得到最后的结论,我们需要进一步细化这个C与L。

此乃一个迭代的步骤,分为连接步和剪枝步,剪枝步是C形成一个新的L的过程,同初始化的剪枝步。需要详细说明的是连接步,这是根据L形成一个新的C的过程,具体如下:

我们需要根据L中存在的项,两两求并集,比如上述的L有4项,求出来的并集肯定有个,但对于求出来的并集,我们必须判断其所有的非空真子集,是否存在于L当中,不存在则要淘汰这个并集,比如上述求出来的一项[I1,I2]的非空真子集[I1]、[I2]都在L能找到,这个[I1,I2]才能压入新的C当中,成为新C的一项。

同时计算出[I1,I2]在D中出现的个数与D的事务之比,也就是[I1,I2]支持度。纪录到C当中。

之后再经历一次剪枝步,剔除支持度少于最小支持度的项,由C形成L。

如此循环,一旦经历完剪枝步之后L为空,上一循环的L则为我们需要的L,具体如下:


三、根据最终的候选集L得出最后的结论

在最终的候选集L中,对里面所有事件进行分析,对其所有互补子集对进行关联分析。比如上述I3,I1,I2的一个互补子集对,[I1,I2],[I3]。我们分别求出其支持度,[I1,I2]的支持度为0.75,[I3]为0.75。这个支持度肯定能从上面的迭代过程的L中得知。

之后如果要判断诸如[I1,I2]->[I3]是否是关联事件,则拿前项的支持度除以后项的支持度,这就是这个事件的置信度。比如[I1,I2]->[I3]的置信度就是1。如果这个置信度大于我们设定的最小置信度,那么它就是一个关联事件。

置信度的意义,如同条件概率,说明前事件在后事件发生时的概率。因为支持度就基本等于该事件发生的概率。Apriori算法的意义就是将一些小概率剔除,逐步淘汰,得到最终的大概率事件。

这里我们设置最小置信度为0.7,那么就能得到一系列关联事件,如下图所示:


这就说明,用户在做完I1的情况下很可能会I2与I3同时做,或者是同时做完I1与I2情况下,会去做I3。


因此,Java的代码如下编写:

import java.io.*;
import java.util.*;

public class Apriori 

	static String filePath = "d:\\\\1.txt";
	static ArrayList<ArrayList<String>> D = new ArrayList<ArrayList<String>>();// 事务数据库
	static HashMap<ArrayList<String>, Double> C = new HashMap<ArrayList<String>, Double>();// 项目集
	static HashMap<ArrayList<String>, Double> L = new HashMap<ArrayList<String>, Double>();// 候选集
	static double min_support = 0.5;// 最小支持度
	static double min_confident = 0.7;// 最小置信度

	// 用于存取候选集每次计算结果,最后计算关联规则,就不用再次遍历事务数据库,这么麻烦了。
	static HashMap<ArrayList<String>, Double> L_ALL = new HashMap<ArrayList<String>, Double>();

	/**
	 * 将txt中的二维表读入T中
	 * 
	 * @param filePath
	 *            TXT的文件路径,注意文件路径分隔符必须写成\\\\
	 * @return ArrayList<ArrayList<String>>,存放每行元素List的List
	 */
	public static ArrayList<ArrayList<String>> readTable(String filePath) 
		ArrayList<ArrayList<String>> T = new ArrayList<ArrayList<String>>();
		Scanner scanner;
		try 
			scanner = new Scanner(new File(filePath));// 指定读入文件
			while (scanner.hasNext()) 
				T.add(new ArrayList<String>(Arrays.asList(scanner.nextLine()
						.split(" "))));
			
			scanner.close();// 必须关闭这个流,否则有警告。
		 catch (FileNotFoundException e) 
			System.out.println("文件不存在!请检查路径。");
		
		return T;
	

	// 剪枝步,删去C少于最小支持度的元素,形成L
	public static void pruning(HashMap<ArrayList<String>, Double> C,
			HashMap<ArrayList<String>, Double> L) 
		L.clear();
		// 根据项目集生成候选集
		L.putAll(C);
		// 删除少于最小支持度的元素
		ArrayList<ArrayList<String>> delete_key = new ArrayList<ArrayList<String>>();
		for (ArrayList<String> key : L.keySet()) 
			if (L.get(key) < min_support) 
				delete_key.add(key);
			
		
		for (int i = 0; i < delete_key.size(); i++) 
			L.remove(delete_key.get(i));
		
	

	/**
	 * 初始化事务数据库、项目集、候选集
	 */
	public static void init() 
		D = readTable(filePath);

		// 扫描事务数据库。生成项目集,支持度=改元素在事务数据库出现的次数/事务数据库的事务数
		for (int i = 0; i < D.size(); i++) 
			for (int j = 0; j < D.get(i).size(); j++) 
				String[] e =  D.get(i).get(j) ;
				ArrayList<String> item = new ArrayList<String>(Arrays.asList(e));
				if (!C.containsKey(item)) 
					C.put(item, 1.0 / D.size());
				 else 
					C.put(item, C.get(item) + 1.0 / D.size());
				
			
		

		pruning(C, L);// 剪枝

		L_ALL.putAll(L);

	

	// 两个整数集求并集
	public static ArrayList<String> arrayListUnion(
			ArrayList<String> arraylist1, ArrayList<String> arraylist2) 
		ArrayList<String> arraylist = new ArrayList<String>();
		arraylist.addAll(arraylist1);
		arraylist.addAll(arraylist2);
		arraylist = new ArrayList<String>(new HashSet<String>(arraylist));
		return arraylist;
	

	/**
	 * 迭代求出最终的候选频繁集
	 * 
	 * @param C
	 *            完成初始化的项目集
	 * @param L
	 *            完成初始化的候选集
	 * @return 最终的候选频繁集
	 */
	public static HashMap<ArrayList<String>, Double> iteration(
			HashMap<ArrayList<String>, Double> C,
			HashMap<ArrayList<String>, Double> L) 
		HashMap<ArrayList<String>, Double> L_temp = new HashMap<ArrayList<String>, Double>();// 用于判断是否结束剪枝的临时变量

		int t = 1;// 迭代次数
		while (L.size() > 0) // 一旦被剪枝后剪干净,剪枝之前则是最终要求的结果。
			t++;
			L_temp.clear();
			L_temp.putAll(L);
			// 一、连接步
			C.clear();
			// 1.将L中的项以一定的规则两两匹配
			ArrayList<ArrayList<String>> L_key = new ArrayList<ArrayList<String>>(
					L.keySet());
			for (int i = 0; i < L_key.size(); i++) 
				for (int j = i + 1; j < L_key.size(); j++) 
					ArrayList<String> C_item = new ArrayList<String>();
					C_item = new ArrayList<String>(arrayListUnion(L_key.get(i),
							L_key.get(j)));
					if (C_item.size() == t) 
						C.put(C_item, 0.0);// 频繁项集的所有非空子集都必须是频繁的
					
				
			
			// 2.通过扫描D,计算此项的支持度
			for (ArrayList<String> key : C.keySet()) 
				for (int i = 0; i < D.size(); i++) 
					if (D.get(i).containsAll(key)) 
						C.put(key, C.get(key) + 1.0 / D.size());
					
				
			
			// System.out.println(C);
			// 二、剪枝步
			pruning(C, L);
			// System.out.println(L);
			// System.out.println("===");

			L_ALL.putAll(L);
		

		return L_temp;
	

	// 求一个集合的所有子集
	public static ArrayList<ArrayList<String>> getSubset(ArrayList<String> L) 
		if (L.size() > 0) 
			ArrayList<ArrayList<String>> result = new ArrayList<ArrayList<String>>();
			for (int i = 0; i < Math.pow(2, L.size()); i++) // 集合子集个数=2的该集合长度的乘方
				ArrayList<String> subSet = new ArrayList<String>();
				int index = i;// 索引从0一直到2的集合长度的乘方-1
				for (int j = 0; j < L.size(); j++) 
					// 通过逐一位移,判断索引那一位是1,如果是,再添加此项
					if ((index & 1) == 1) // 位与运算,判断最后一位是否为1
						subSet.add(L.get(j));
					
					index >>= 1;// 索引右移一位
				
				result.add(subSet); // 把子集存储起来
			
			return result;
		 else 
			return null;
		
	

	// 判断两个集合相交是否为空
	public static boolean intersectionIsNull(ArrayList<String> l1,
			ArrayList<String> l2) 
		Set<String> s1 = new HashSet<String>(l1);
		Set<String> s2 = new HashSet<String>(l2);

		s1.retainAll(s2);
		if (s1.size() > 0) 
			return false;
		 else 
			return true;
		
	

	/**
	 * 根据最终的关联集,根据公式计算出各个关联事件
	 */
	public static void connection() 
		for (ArrayList<String> key : L.keySet()) // 对最终的关联集各个事件进行判断
			ArrayList<ArrayList<String>> key_allSubset = getSubset(key);
			// System.out.println(key_allSubset);
			for (int i = 0; i < key_allSubset.size(); i++) 
				ArrayList<String> item_pre = key_allSubset.get(i);
				if (0 < item_pre.size() && item_pre.size() < key.size()) // 求其非空真子集
					// 各个非空互补真子集之间形成关联事件
					double item_pre_support = L_ALL.get(item_pre);
					for (int j = 0; j < key_allSubset.size(); j++) 
						ArrayList<String> item_post = key_allSubset.get(j);
						if (0 < item_post.size()
								&& item_post.size() < key.size()
								&& arrayListUnion(item_pre, item_post).equals(
										key)
								&& intersectionIsNull(item_pre, item_post)) 
							double item_post_support = L_ALL.get(item_post);// 互补真子集的支持度比则是事件的置信度
							double confident = item_pre_support
									/ item_post_support; // 事件的置信度
							if (confident > min_confident) // 如果事件的置信度大于最小置信度
								System.out
										.println(item_pre + "==>" + item_post);// 则是一个关联事件
								// System.out.println(item_pre_support + "==>" +
								// item_post_support);
							
						

					
				
			
		
	

	public static void main(String[] args) 

		init();
		/*
		 * System.out.println(D); System.out.println(C); System.out.println(L);
		 * System.out.println("===");
		 */
		L = iteration(C, L);
		/*
		 * System.out.println(L); System.out.println(L_ALL);
		 * System.out.println("===");
		 */
		connection();

	


其中,读入的txt用到《【Java】一行代码读完记事本中的二维表》( 点击打开链接)中所提到的东西。

以上是关于JavaApriori算法的主要内容,如果未能解决你的问题,请参考以下文章

python 操作符** (两个乘号就是乘方)

Python|利用递归轻松解决数的乘方问题

归纳总结MATLAB中与矩阵运算有关的算术运算符(加减乘除点乘点除乘方转置等)

数据结构 栈解析 算法表达式

[操作系统]Nachos系统调用的实现(乘法除法与乘方)

[操作系统]Nachos系统调用的实现(乘法除法与乘方)