5/31 AprioriAll算法研究(2)

历经饱受折磨的一天,我总算是实现出来了这个算法。实话说看懂了之后实现起来困难不是特别大,但就是相当的繁琐。这个算法步骤太多,因此代码量也很多,好在寻找频繁项集的过程和寻找频繁序列的过程比较像,写起来稍稍有些宽慰。

另外,这里是用的我更熟悉的java写的(但是说起来一点也不面向对象了),而且也几乎写了逐行注释,最后加起来有六百行。。。

package aprioriAll;

import java.io.*;
import java.util.*;
import java.util.Map.Entry;

public class AprioriAll {

	public static void main(String[] args) {
		AprioriAllCalculation ap = new AprioriAllCalculation();

		ap.aprioriAllProcess();
	}
}

class AprioriAllCalculation

{
	/**
	 * 三维数组,代表一个序列数据库,维度是:事务,序列,事件桶(用户,周,访问列表)
	 */
	Vector>> data = null;
	
	/**
	 * 事件列表
	 */
	Vector itemList = null;
	
	/**
	 * 频繁n-序列集合	
	 */
	Vector> litemset = null;
	
	/**
	 * 序列到整数映射
	 */
	HashMap, Integer> litemMaps = null;
	/**
	 * 把事件转换为频繁项集后的客户信息序列
	 */
	Vector>>> transformedSequence = null;
	/**
	 * 转换后再映射到整数的客户信息序列
	 */
	Vector>> transformedMappedSequence = null;
	Vector> seqItemset = null;
	/**
	 * 频繁序列候选集
	 */
	Vector> seqCandidates = new Vector>();
	Vector mappedItemList = null;
	/**
	 * 记录每个客户所包含的1-频繁序列
	 */
	Vector> seqData = null;
	/**
	 * 最大频繁序列
	 */
	Vector> maximalLargeSequence = null;
	/**
	 * 结果集
	 */
	Vector>> resultSet = null;

	/**
	 * 当前频繁n-项集的候选项集集合
	 */
	Vector> candidates = new Vector>();
	
	String configFile = "src/aprioriAll/config.txt"; // 配置文件
	String transaFile = "src/aprioriAll/transa.txt"; // 数据文件
	String outputFile = "src/aprioriAll/aprioriAll-output.txt";// 输出文件
	int numItems; // n-计数用
	int numTransactions; // 事务数
	double minSupRatio; // 最小支持度
	double minSupNumber; // 最小支持频数
	String itemSep = " "; // 数据库中每行的分隔符

	public void aprioriAllProcess() {
		getConfig(); // 获取配置 用户个数和最小支持度

		// 排序阶段
		System.out.println("...Sort Phase....\n");
		SortPhase();
		System.out.println("Phase 1 is completed\n");
		System.out.println("data : " + data + "\n\n");
		// 频繁项集阶段
		System.out.println("...Litem Phase....\n");
		LitemPhase();
		System.out.println("Phase 2 is completed\n");
		System.out.println("litemset : " + litemset + "\n\n");
		// 把项映射成整数,方便计算
		MapCreation();
		//转换阶段
		System.out.println("...Transformation Phase....\n");
		TransformationPhase();
		System.out.println("Phase 3 is completed\n");
		System.out.println("mapped sequence : " + transformedMappedSequence + "\n\n");
		//序列阶段
		System.out.println("...Sequence Phase....\n");
		SequencePhase();
		System.out.println("Phase 4 is completed\n");
		System.out.println("Sequence Item Set : " + seqItemset + "\n\n");
		//最大化阶段
		System.out.println("...Maximal Phase....\n");
		MaximalPhase();
		System.out.println("Phase 5 is completed\n");

		System.out.println("Result is : \n" + resultSet);
	}

	/**
	 * 获取配置
	 * 包括事务(用户)个数和最小支持度
	 */
	private void getConfig() {
		FileWriter fw;
		BufferedWriter file_out;
		try {
			FileInputStream file_in = new FileInputStream(configFile);
			BufferedReader data_in = new BufferedReader(new InputStreamReader(file_in));

			// 事务(用户)数
			numTransactions = Integer.valueOf(data_in.readLine()).intValue();
			// 最小支持度(百分比)
			minSupRatio = (Double.valueOf(data_in.readLine()).doubleValue());

			// 输出到控制台
			System.out.print("\nInput configuration: " + numItems + " items, " + numTransactions + " transactions, ");
			System.out.println("minsup = " + minSupRatio + "%");
			System.out.println();
			minSupNumber = minSupRatio / 100 * numTransactions;

			// 创建输出文件
			fw = new FileWriter(outputFile);
			file_out = new BufferedWriter(fw);
			// 输出测试 事务数
			file_out.write(numTransactions + "\n");
			file_out.close();
		} catch (IOException e) {
			System.out.println(e);
		}
	}

	/**
	 * 排序阶段
	 * 读取数据库中的数据,生成一个以三维数组表示的数据集
	 */
	private void SortPhase() {

		data = new Vector>>();
		itemList = new Vector();
		FileInputStream file_in; // 文件输入流
		BufferedReader data_in; // 数据输入流
		StringTokenizer stFile;

		try {
			// 加载数据文件
			file_in = new FileInputStream(transaFile);
			data_in = new BufferedReader(new InputStreamReader(file_in));

			int i = 0;
			while (i < numTransactions) {
				// 获取序列
				Vector> sequence = new Vector>();
				while (true) {
					stFile = new StringTokenizer(data_in.readLine(), itemSep);
					if (stFile.countTokens() == 0) {
						break;
					} else {
						// 获取序列中的事件桶
						Vector basket = new Vector();
						while (stFile.hasMoreTokens()) {
							// 获取事件桶中的事件并添加
							String item = stFile.nextToken();
							basket.add(item);
							// 同时构建事件列表(1-项集)
							if (!itemList.contains(item)) {
								itemList.add(item);
							}
						}
						// 添加到序列
						sequence.add(basket);
					}
				}
				// 添加到事务集
				data.add(sequence);
				i++;
			}
		}

		catch (IOException e) {
			System.out.println(e);
		}

	}

	/**
	 * 频繁项集阶段
	 * 从1-项集开始,寻找符合最小支持度的候选项
	 * 自连接产生下一项集,寻找符合最小支持度的候选项
	 * 循环直到自连接不出来下一项集
	 * 这样生成出来了频繁1-序列
	 */
	private void LitemPhase() {

		int itemsetNumber = 0; // 当前是n-项集
		numItems = itemList.size();

		litemset = new Vector>();
		System.out.println("Apriori algorithm has started for litem phase...\n");

		// while not complete
		do {
			// 计数当前要检查n-项集
			itemsetNumber++;

			// 生成候选集
			generateCandidates(itemsetNumber);

			// 检查支持度
			calculateFrequentItemsets(itemsetNumber);
			// 频繁n-项集
			if (candidates.size() != 0) {
				System.out.println("Frequent " + itemsetNumber + "-itemsets");
				System.out.println(candidates);
			}
			//把频繁n-项集中的候选添加到频繁序列中
			litemset.addAll(candidates);
			// 如果频繁项集中小于等于1项,那就已经结束了
		} while (candidates.size() > 1);

	}

	/**
	 * 生成频繁1-序列和整数的映射
	 */
	private void MapCreation() {

		litemMaps = new HashMap, Integer>();
		for (int i = 1; i < litemset.size() + 1; i++) {
			litemMaps.put(litemset.get(i - 1), i);
		}

	}

	/**
	 * 转换阶段
	 * 每个事件被包含于该事件中所有频繁项集替换。
	 * 如果一个事件不包含任何频繁项集,则将其删除。
	 * 如果一个客户序列不包含任何频繁项集,则将该序列删除。
	 */
	private void TransformationPhase() {

		transformedMappedSequence = new Vector>>();
		transformedSequence = new Vector>>>();
		seqData = new Vector>();

		// 对每一个客户
		for (int i = 0; i < data.size(); i++) {
			int count = 0;
			transformedSequence.add(new Vector>>());
			transformedMappedSequence.add(new Vector>());
			seqData.add(new Vector());
			// 对每一个序列
			for (int j = 0; j < data.get(i).size(); j++) {
				transformedSequence.get(i).add(new Vector>());
				transformedMappedSequence.get(i).add(new Vector());
				// 检查频繁1-序列中的每个序列
				for (int k = 0; k < litemset.size(); k++) {
					// 如果这个客户的这个序列的事件集中包含这个频繁序列
					if (data.get(i).get(j).containsAll(litemset.get(k))) {
						// 把这个频繁序列记录到这个客户的这个事件集中(j-count是因为有需要删除的事件集)
						transformedSequence.get(i).get(j - count).add(litemset.get(k));
						// 映射成整数的那个也加上
						transformedMappedSequence.get(i).get(j - count).add(litemMaps.get(litemset.get(k)));
						seqData.get(i).add(litemMaps.get(litemset.get(k)));
					}
				}
				// 如果检查完频繁序列集 发现这个客户的这个事件集里没有频繁序列
				if (transformedSequence.get(i).get(j - count).isEmpty()) {
					// 那么删除这个事件集
					transformedSequence.get(i).remove(j - count);
					transformedMappedSequence.get(i).remove(j - count);
					// 计数
					count++;
				}
			}
		}
	}

	/**
	 * 产生频繁序列阶段
	 * 利用转换后的序列数据库寻找频繁序列
	 */
	public void SequencePhase() {

		// 记录现在是n-序列
		int itemsetNumber = 0;

		seqItemset = new Vector>();

		System.out.println("Apriori algorithm has started for Sequence Phase\n");

		do {
			itemsetNumber++;
			// 生成候选集
			generateSeqCandidates(itemsetNumber);
			// 检查支持度
			calculateSeqFrequentItemsets(itemsetNumber);
			if (seqCandidates.size() != 0) {
				System.out.println("Frequent " + itemsetNumber + "-itemsets");
				System.out.println(seqCandidates);
			}
			// 在频繁序列集中添加频繁序列候选集
			seqItemset.addAll(seqCandidates);
			// 和检查频繁项集类似 直到频繁候选集中小于等于一个,就代表结束了
		} while (seqCandidates.size() > 1);

	}

	/**
	 * 生成n项集的候选集
	 * @param n 从n-项集开始生成
	 */
	private void generateCandidates(int n) {

		Vector> tempCandidates = new Vector>(); // 临时候选集
		Vector tempElementVec;
		Vector tempElementVec2;
		// 如果是1-项集,那就是所有的事件
		if (n == 1) {
			for (int i = 0; i < numItems; i++) {
				tempElementVec = new Vector();
				tempElementVec.add(itemList.elementAt(i));
				tempCandidates.add(tempElementVec);
			}
		} else if (n == 2) // 如果是2项集,那就是1项集两两组合
		{
			

			for (int i = 0; i < candidates.size(); i++)
				for (int j = i + 1; j < candidates.size(); j++) {
					tempElementVec = new Vector();
					tempElementVec.add(candidates.get(i).get(0));
					tempElementVec.add(candidates.get(j).get(0));
					tempCandidates.add(tempElementVec);

				}

		} else { 	
			// 对于其他项集,需要做自连接,即检查每一(n-1)-项和之后的另一(n-1)-项,如果这两个的前n-2项都一样,就可以做连接
			// 例如生成3-项集,已有2-项集是[[40,70],[40,80]],检查他们俩的前1项,一样,就把两个的最后一项也合起来得到[40,70,80]

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

				for (int j = i + 1; j < candidates.size(); j++) {
					tempElementVec = new Vector();
					tempElementVec2 = new Vector();

					for (int s = 0; s < n - 2; s++) {
						tempElementVec.add(candidates.get(i).get(s));
						tempElementVec2.add(candidates.get(j).get(s));
					}

					if (tempElementVec.equals(tempElementVec2)) {
						tempElementVec.add(candidates.get(i).get(n - 2));
						tempElementVec.add(candidates.get(j).get(n - 2));
						tempCandidates.add(tempElementVec);
					}
				}
			}
		}

		candidates.clear();

		candidates = new Vector>(tempCandidates);
		tempCandidates.clear();
	}

	/**
	 * 计算候选集中各个候选项集是否符合最小支持度
	 * @param n n-项集
	 */
	private void calculateFrequentItemsets(int n) {

		Vector> TempCandidates = new Vector>();
		Boolean[] flags;
		int count;
		// 对每一候选项集
		for (Vector cand : candidates) {

			flags = new Boolean[data.size()];
			// 对数据集中每一顾客
			for (Vector> customer : data) {

				int a = data.indexOf(customer);
				// 对每一事务的每一事件桶
				for (Vector basket : customer) {
					// 如果包含候选集
					if (basket.containsAll(cand)) {
						// 标记包含
						flags[a] = true;
						break;

					}

				}
				if (flags[a] == null)
					flags[a] = false;

			}
			// 统计
			count = 0;
			for (Boolean flag : flags)
				if (flag)
					count++;
			// 如果大于最小支持度则留下
			if (count >= minSupNumber)
				TempCandidates.add(cand);

		}


		candidates.clear();

		candidates = new Vector>(TempCandidates);
		TempCandidates.clear();

	}

	/**
	 * 由n-1序列生成n序列
	 * @param n
	 */
	private void generateSeqCandidates(int n) {

		// 储存当前候选序列
		Vector> tempCandidates = new Vector>(); 
		Vector tempElementVec;
		Vector tempElementVec2;
		// 如果是1-序列,那么里面就是所有的代表频繁序列的整数(类似找频繁项集)
		if (n == 1) {
			for (int i = 1; i <= litemMaps.size(); i++) {
				tempElementVec = new Vector();
				tempElementVec.add(i);
				tempCandidates.add(tempElementVec);
			}
		}

		else if (n == 2) // 2-序列就是1-序列两两组合
		{


			for (int i = 0; i < seqCandidates.size(); i++)
				for (int j = i + 1; j < seqCandidates.size(); j++) {
					tempElementVec = new Vector();
					tempElementVec.add(seqCandidates.get(i).get(0));
					tempElementVec.add(seqCandidates.get(j).get(0));
					tempCandidates.add(tempElementVec);

				}

		} else {
			// 其他n-序列
			for (int i = 0; i < seqCandidates.size(); i++) {
				// 每一个和后面的比较 类似于n-项集
				for (int j = i + 1; j < seqCandidates.size(); j++) {
					tempElementVec = new Vector();
					tempElementVec2 = new Vector();

					for (int s = 0; s < n - 2; s++) {
						tempElementVec.add(seqCandidates.get(i).get(s));
						tempElementVec2.add(seqCandidates.get(j).get(s));
					}

					if (tempElementVec.equals(tempElementVec2)) {
						tempElementVec.add(seqCandidates.get(i).get(n - 2));
						tempElementVec.add(seqCandidates.get(j).get(n - 2));
						tempCandidates.add(tempElementVec);
					}
				}
			}
		}
		// 清空
		seqCandidates.clear();
		// 刷新
		seqCandidates = new Vector>(tempCandidates);
		tempCandidates.clear();
	}
	/**
	 * 计算频繁序列 是否符合支持度
	 * @param n
	 */
	private void calculateSeqFrequentItemsets(int n) {

		Vector> TempCandidates = new Vector>();
		Boolean[] flags;
		int count;
		// 对每一个候选序列
		for (Vector can : seqCandidates) {

			flags = new Boolean[seqData.size()];
			// 对每一个序列数据集中的用户包含的序列
			for (int i = 0; i < seqData.size(); i++) {
				Vector cand = new Vector(can);
				Vector customer = new Vector(seqData.get(i));
				while (cand.size() > 0 && customer.size() > 0) {
					// 检查是否可以匹配到这个序列(用了一种“跳过不匹配”的巧妙方式)
					if (cand.get(0).equals(customer.get(0))) {
						cand.remove(0);
					} else {
						customer.remove(0);
					}
				}
				// 如果频繁序列每一个都匹配到了那么标志为有
				if (cand.size() == 0)
					flags[i] = true;
				else
					flags[i] = false;

			}
			// 计数
			count = 0;
			for (Boolean flag : flags)
				if (flag)
					count++;

			if (count >= minSupNumber)
				TempCandidates.add(can);
		}

		// 清空
		seqCandidates.clear();
		// 把临时的候选序列赋值过来
		seqCandidates = new Vector>(TempCandidates);
		TempCandidates.clear();

	}

	/**
	 * 最大化阶段
	 * 目的是把频繁序列集中的 可以认为是其他频繁序列集的子序列集 的序列集 删去
	 */
	private void MaximalPhase() {
		// 找最长序列
		maximalLargeSequence = new Vector>();
		Vector> tempSequenceSet = new Vector>(seqItemset);

		// 对频繁序列集中的每个频繁序列
		for (Vector seqs : seqItemset) {
			int check = 0;
			// 拿出来一个
			tempSequenceSet.remove(seqs);

			// 对剩下的每一个
			for (Vector checkSeq : tempSequenceSet) {
				// 检查是否包含拿出来那个
				if (checkSeq.containsAll(seqs)) {
					check = 1;
				}
			}
			// 都不包含就放到最大序列里
			if (check == 0) {
				maximalLargeSequence.add(seqs);
			}
		}
		// 按最大序列的整数在map里查找出来原来的值
		resultSet = new Vector>>();
		// 对最大序列中的每一个序列
		for (Vector seq : maximalLargeSequence) {
			resultSet.add(new Vector>());
			for (int i = 0; i < seq.size(); i++) {
				for (Entry, Integer> ent : litemMaps.entrySet()) {
					if (ent.getValue() == seq.get(i)) {
						resultSet.get(maximalLargeSequence.indexOf(seq)).add(ent.getKey());
					}
				}
			}
		}

	}

	/**
	 * 输入方法
	 * @return 文本文档中的一行
	 */
	public static String getInput() {
		String input = "";
		
		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

		
		try {
			input = reader.readLine();
		} catch (Exception e) {
			System.out.println(e);
		}
		return input;
	}

}

跑个测试样例看看:

30 
90 

10 20 
30 
40 60 70

30 50 70

30
40 70
90

90

5/31 AprioriAll算法研究(2)_第1张图片

OK,成功找到最大频繁序列。下一步的工作就是基于频繁序列的推荐了,目前有两个思路,一个是检查一个用户的近期事务(例如本周,或者最近两周),是否是某一个最大频繁序列的子序列,如果是,推荐该最大频繁序列中,用户出现过序列之后的事件;另一个是检索用户历史事务中的每一个事件集,对于每一个事件集所在的最大频繁序列之后的事件集都计数一遍,按从多到少排序来推荐。这两个粗略一想,前一个是推荐序列,更符合最初知识图谱、学习路径的思路,后一个有种多个路径交叉点的感觉,可能推荐出来是比较重要的节点。明后两天实现出来看看效果吧。

你可能感兴趣的:(资源聚合平台)