gSpan频繁子图挖掘算法

参考资料:http://www.cs.ucsb.edu/~xyan/papers/gSpan.pdf

http://www.cs.ucsb.edu/~xyan/papers/gSpan-short.pdf

http://www.jos.org.cn/1000-9825/18/2469.pdf

http://blog.csdn.net/coolypf/article/details/8263176

更多挖掘算法:https://github.com/linyiqun/DataMiningAlgorithm

介绍

gSpan算法是图挖掘邻域的一个算法,而作为子图挖掘算法,又是其他图挖掘算法的基础,所以gSpan算法在图挖掘算法中还是非常重要的。gSpan算法在挖掘频繁子图的时候,用了和FP-grown中相似的原理,就是Pattern-Grown模式增长的方式,也用到了最小支持度计数作为一个过滤条件。图算法在程序上比其他的算法更加的抽象,在实现时更加需要空间想象能力。gSpan算法的核心就是给定n个图,然后从中挖掘出频繁出现的子图部分。

算法原理

说实话,gSpan算法在我最近学习的算法之中属于非常难的那种,因为要想实现他,必须要明白他的原理,而这就要花很多时间去明白算法的一些定义,比如dfs编码,最右路径这样的概念。所以,我们应该先知道算法整体的一个结构。

1、遍历所有的图,计算出所有的边和点的频度。

2、将频度与最小支持度数做比较,移除不频繁的边和点。

3、重新将剩下的点和边按照频度进行排序,将他们的排名号给边和点进行重新标号。

4、再次计算每条边的频度,计算完后,然后初始化每条边,并且进行此边的subMining()挖掘过程。

subMining的过程

1、根据graphCode重新恢复当前的子图

2、判断当前的编码是否为最小dfs编码,如果是加入到结果集中,继续在此基础上尝试添加可能的边,进行继续挖掘

3、如果不是最小编码,则此子图的挖掘过程结束。

DFS编码

gSpan算法对图的边进行编码,采用E(v0,v1,A,B,a)的方式,v0,v1代表的标识,你可以看做就是点的id,A,B可以作为点的标号,a为之间的边的标号,而一个图就是由这样的边构成的,G{e1, e2, e3,.....},而dfs编码的方式就是比里面的五元组的元素,我这里采用的规则是,从左往右依次比较大小,如果谁先小于另一方,谁就算小,图的比较算法同样如此,具体的规则可以见我后面代码中的注释。但是这个规则并不是完全一致的,至少在我看的相关论文中有不一样的描述存在。

生成subGraph

生成子图的进行下一次挖掘的过程也是gSpan算法中的一个难点,首先你要对原图进行编码,找到与挖掘子图一致的编码,找到之后,在图的最右路径上寻找可以扩展的边,在最右路径上扩展的情况分为2种,1种为在最右节点上进行扩展,1种为在最右路径的点上进行扩展。2种情况都需要做一定的判断。

算法的技巧

算法在实现时,用的技巧比较多,有些也很不好理解,比如在dfs编码或找子边的过程中,用到了图id对于Edge中的五元组id的映射,这个会一开始没想到,还有怎么去描述一个图通过一定的数据结构。

算法的实现

此算法是借鉴了网上其他版本的实现,我是在看懂了人家代码的基础上,自己对其中的某些部分作了修改之后的。由于代码比较多,下面给出核心代码,全部代码在这里

GSpanTool.java:

package DataMining_GSpan;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * gSpan频繁子图挖掘算法工具类
 *
 * @author lyq
 *
 */
public class GSpanTool {
	// 文件数据类型
	public final String INPUT_NEW_GRAPH = "t";
	public final String INPUT_VERTICE = "v";
	public final String INPUT_EDGE = "e";
	// Label标号的最大数量,包括点标号和边标号
	public final int LABEL_MAX = 100;

	// 测试数据文件地址
	private String filePath;
	// 最小支持度率
	private double minSupportRate;
	// 最小支持度数,通过图总数与最小支持度率的乘积计算所得
	private int minSupportCount;
	// 初始所有图的数据
	private ArrayList<GraphData> totalGraphDatas;
	// 所有的图结构数据
	private ArrayList<Graph> totalGraphs;
	// 挖掘出的频繁子图
	private ArrayList<Graph> resultGraphs;
	// 边的频度统计
	private EdgeFrequency ef;
	// 节点的频度
	private int[] freqNodeLabel;
	// 边的频度
	private int[] freqEdgeLabel;
	// 重新标号之后的点的标号数
	private int newNodeLabelNum = 0;
	// 重新标号后的边的标号数
	private int newEdgeLabelNum = 0;

	public GSpanTool(String filePath, double minSupportRate) {
		this.filePath = filePath;
		this.minSupportRate = minSupportRate;
		readDataFile();
	}

	/**
	 * 从文件中读取数据
	 */
	private void readDataFile() {
		File file = new File(filePath);
		ArrayList<String[]> dataArray = new ArrayList<String[]>();

		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			String[] tempArray;
			while ((str = in.readLine()) != null) {
				tempArray = str.split(" ");
				dataArray.add(tempArray);
			}
			in.close();
		} catch (IOException e) {
			e.getStackTrace();
		}

		calFrequentAndRemove(dataArray);
	}

	/**
	 * 统计边和点的频度,并移除不频繁的点边,以标号作为统计的变量
	 *
	 * @param dataArray
	 *            原始数据
	 */
	private void calFrequentAndRemove(ArrayList<String[]> dataArray) {
		int tempCount = 0;
		freqNodeLabel = new int[LABEL_MAX];
		freqEdgeLabel = new int[LABEL_MAX];

		// 做初始化操作
		for (int i = 0; i < LABEL_MAX; i++) {
			// 代表标号为i的节点目前的数量为0
			freqNodeLabel[i] = 0;
			freqEdgeLabel[i] = 0;
		}

		GraphData gd = null;
		totalGraphDatas = new ArrayList<>();
		for (String[] array : dataArray) {
			if (array[0].equals(INPUT_NEW_GRAPH)) {
				if (gd != null) {
					totalGraphDatas.add(gd);
				}

				// 新建图
				gd = new GraphData();
			} else if (array[0].equals(INPUT_VERTICE)) {
				// 每个图中的每种图只统计一次
				if (!gd.getNodeLabels().contains(Integer.parseInt(array[2]))) {
					tempCount = freqNodeLabel[Integer.parseInt(array[2])];
					tempCount++;
					freqNodeLabel[Integer.parseInt(array[2])] = tempCount;
				}

				gd.getNodeLabels().add(Integer.parseInt(array[2]));
				gd.getNodeVisibles().add(true);
			} else if (array[0].equals(INPUT_EDGE)) {
				// 每个图中的每种图只统计一次
				if (!gd.getEdgeLabels().contains(Integer.parseInt(array[3]))) {
					tempCount = freqEdgeLabel[Integer.parseInt(array[3])];
					tempCount++;
					freqEdgeLabel[Integer.parseInt(array[3])] = tempCount;
				}

				int i = Integer.parseInt(array[1]);
				int j = Integer.parseInt(array[2]);

				gd.getEdgeLabels().add(Integer.parseInt(array[3]));
				gd.getEdgeX().add(i);
				gd.getEdgeY().add(j);
				gd.getEdgeVisibles().add(true);
			}
		}
		// 把最后一块gd数据加入
		totalGraphDatas.add(gd);
		minSupportCount = (int) (minSupportRate * totalGraphDatas.size());

		for (GraphData g : totalGraphDatas) {
			g.removeInFreqNodeAndEdge(freqNodeLabel, freqEdgeLabel,
					minSupportCount);
		}
	}

	/**
	 * 根据标号频繁度进行排序并且重新标号
	 */
	private void sortAndReLabel() {
		int label1 = 0;
		int label2 = 0;
		int temp = 0;
		// 点排序名次
		int[] rankNodeLabels = new int[LABEL_MAX];
		// 边排序名次
		int[] rankEdgeLabels = new int[LABEL_MAX];
		// 标号对应排名
		int[] nodeLabel2Rank = new int[LABEL_MAX];
		int[] edgeLabel2Rank = new int[LABEL_MAX];

		for (int i = 0; i < LABEL_MAX; i++) {
			// 表示排名第i位的标号为i,[i]中的i表示排名
			rankNodeLabels[i] = i;
			rankEdgeLabels[i] = i;
		}

		for (int i = 0; i < freqNodeLabel.length - 1; i++) {
			int k = 0;
			label1 = rankNodeLabels[i];
			temp = label1;
			for (int j = i + 1; j < freqNodeLabel.length; j++) {
				label2 = rankNodeLabels[j];

				if (freqNodeLabel[temp] < freqNodeLabel[label2]) {
					// 进行标号的互换
					temp = label2;
					k = j;
				}
			}

			if (temp != label1) {
				// 进行i,k排名下的标号对调
				temp = rankNodeLabels[k];
				rankNodeLabels[k] = rankNodeLabels[i];
				rankNodeLabels[i] = temp;
			}
		}

		// 对边同样进行排序
		for (int i = 0; i < freqEdgeLabel.length - 1; i++) {
			int k = 0;
			label1 = rankEdgeLabels[i];
			temp = label1;
			for (int j = i + 1; j < freqEdgeLabel.length; j++) {
				label2 = rankEdgeLabels[j];

				if (freqEdgeLabel[temp] < freqEdgeLabel[label2]) {
					// 进行标号的互换
					temp = label2;
					k = j;
				}
			}

			if (temp != label1) {
				// 进行i,k排名下的标号对调
				temp = rankEdgeLabels[k];
				rankEdgeLabels[k] = rankEdgeLabels[i];
				rankEdgeLabels[i] = temp;
			}
		}

		// 将排名对标号转为标号对排名
		for (int i = 0; i < rankNodeLabels.length; i++) {
			nodeLabel2Rank[rankNodeLabels[i]] = i;
		}

		for (int i = 0; i < rankEdgeLabels.length; i++) {
			edgeLabel2Rank[rankEdgeLabels[i]] = i;
		}

		for (GraphData gd : totalGraphDatas) {
			gd.reLabelByRank(nodeLabel2Rank, edgeLabel2Rank);
		}

		// 根据排名找出小于支持度值的最大排名值
		for (int i = 0; i < rankNodeLabels.length; i++) {
			if (freqNodeLabel[rankNodeLabels[i]] > minSupportCount) {
				newNodeLabelNum = i;
			}
		}
		for (int i = 0; i < rankEdgeLabels.length; i++) {
			if (freqEdgeLabel[rankEdgeLabels[i]] > minSupportCount) {
				newEdgeLabelNum = i;
			}
		}
		//排名号比数量少1,所以要加回来
		newNodeLabelNum++;
		newEdgeLabelNum++;
	}

	/**
	 * 进行频繁子图的挖掘
	 */
	public void freqGraphMining() {
		long startTime =  System.currentTimeMillis();
		long endTime = 0;
		Graph g;
		sortAndReLabel();

		resultGraphs = new ArrayList<>();
		totalGraphs = new ArrayList<>();
		// 通过图数据构造图结构
		for (GraphData gd : totalGraphDatas) {
			g = new Graph();
			g = g.constructGraph(gd);
			totalGraphs.add(g);
		}

		// 根据新的点边的标号数初始化边频繁度对象
		ef = new EdgeFrequency(newNodeLabelNum, newEdgeLabelNum);
		for (int i = 0; i < newNodeLabelNum; i++) {
			for (int j = 0; j < newEdgeLabelNum; j++) {
				for (int k = 0; k < newNodeLabelNum; k++) {
					for (Graph tempG : totalGraphs) {
						if (tempG.hasEdge(i, j, k)) {
							ef.edgeFreqCount[i][j][k]++;
						}
					}
				}
			}
		}

		Edge edge;
		GraphCode gc;
		for (int i = 0; i < newNodeLabelNum; i++) {
			for (int j = 0; j < newEdgeLabelNum; j++) {
				for (int k = 0; k < newNodeLabelNum; k++) {
					if (ef.edgeFreqCount[i][j][k] >= minSupportCount) {
						gc = new GraphCode();
						edge = new Edge(0, 1, i, j, k);
						gc.getEdgeSeq().add(edge);

						// 将含有此边的图id加入到gc中
						for (int y = 0; y < totalGraphs.size(); y++) {
							if (totalGraphs.get(y).hasEdge(i, j, k)) {
								gc.getGs().add(y);
							}
						}
						// 对某条满足阈值的边进行挖掘
						subMining(gc, 2);
					}
				}
			}
		}

		endTime = System.currentTimeMillis();
		System.out.println("算法执行时间"+ (endTime-startTime) + "ms");
		printResultGraphInfo();
	}

	/**
	 * 进行频繁子图的挖掘
	 *
	 * @param gc
	 *            图编码
	 * @param next
	 *            图所含的点的个数
	 */
	public void subMining(GraphCode gc, int next) {
		Edge e;
		Graph graph = new Graph();
		int id1;
		int id2;

		for(int i=0; i<next; i++){
			graph.nodeLabels.add(-1);
			graph.edgeLabels.add(new ArrayList<Integer>());
			graph.edgeNexts.add(new ArrayList<Integer>());
		}

		// 首先根据图编码中的边五元组构造图
		for (int i = 0; i < gc.getEdgeSeq().size(); i++) {
			e = gc.getEdgeSeq().get(i);
			id1 = e.ix;
			id2 = e.iy;

			graph.nodeLabels.set(id1, e.x);
			graph.nodeLabels.set(id2, e.y);
			graph.edgeLabels.get(id1).add(e.a);
			graph.edgeLabels.get(id2).add(e.a);
			graph.edgeNexts.get(id1).add(id2);
			graph.edgeNexts.get(id2).add(id1);
		}

		DFSCodeTraveler dTraveler = new DFSCodeTraveler(gc.getEdgeSeq(), graph);
		dTraveler.traveler();
		if (!dTraveler.isMin) {
			return;
		}

		// 如果当前是最小编码则将此图加入到结果集中
		resultGraphs.add(graph);
		Edge e1;
		ArrayList<Integer> gIds;
		SubChildTraveler sct;
		ArrayList<Edge> edgeArray;
		// 添加潜在的孩子边,每条孩子边所属的图id
		HashMap<Edge, ArrayList<Integer>> edge2GId = new HashMap<>();
		for (int i = 0; i < gc.gs.size(); i++) {
			int id = gc.gs.get(i);

			// 在此结构的条件下,在多加一条边构成子图继续挖掘
			sct = new SubChildTraveler(gc.edgeSeq, totalGraphs.get(id));
			sct.traveler();
			edgeArray = sct.getResultChildEdge();

			// 做边id的更新
			for (Edge e2 : edgeArray) {
				if (!edge2GId.containsKey(e2)) {
					gIds = new ArrayList<>();
				} else {
					gIds = edge2GId.get(e2);
				}

				gIds.add(id);
				edge2GId.put(e2, gIds);
			}
		}

		for (Map.Entry entry : edge2GId.entrySet()) {
			e1 = (Edge) entry.getKey();
			gIds = (ArrayList<Integer>) entry.getValue();

			// 如果此边的频度大于最小支持度值,则继续挖掘
			if (gIds.size() < minSupportCount) {
				continue;
			}

			GraphCode nGc = new GraphCode();
			nGc.edgeSeq.addAll(gc.edgeSeq);
			// 在当前图中新加入一条边,构成新的子图进行挖掘
			nGc.edgeSeq.add(e1);
			nGc.gs.addAll(gIds);

			if (e1.iy == next) {
				// 如果边的点id设置是为当前最大值的时候,则开始寻找下一个点
				subMining(nGc, next + 1);
			} else {
				// 如果此点已经存在,则next值不变
				subMining(nGc, next);
			}
		}
	}

	/**
	 * 输出频繁子图结果信息
	 */
	public void printResultGraphInfo(){
		System.out.println(MessageFormat.format("挖掘出的频繁子图的个数为:{0}个", resultGraphs.size()));
	}

}

这个算法在后来的实现时,渐渐的发现此算法的难度大大超出我预先的设想,不仅仅是其中的抽象性,还在于测试的复杂性,对于测试数据的捏造,如果用的是真实数据测的话,数据量太大,自己造数据拿捏的也不是很准确。我最后也只是自己伪造了一个图的数据,挖掘了其中的一条边的情况。大致的走了一个过程。代码并不算是完整的,仅供学习。

算法的缺点

在后来实现完算法之后,我对于其中的小的过程进行了分析,发现这个算法在2个深度优先遍历的过程中还存在问题,就是DFS判断是否最小编码和对原图进行寻找相应编码,的时候,都只是限于Edge中边是连续的情况,如果不连续了,会出现判断出错的情况,因为在最右路径上添加边,就是会出现在前面的点中多扩展一条边,就不会是连续的。而在上面的代码中是无法处理这样的情况的,个人的解决办法是用栈的方式,将节点压入栈中实现最好。

算法的体会

这个算法花了很多的时间,关关理解这个算法就已经不容易了,经常需要我在脑海中去刻画这样的图形和遍历的一些情况,带给我的挑战还是非常的大吧。

算法的特点

此算法与FP-Tree算法类似,在挖掘的过程中也是没有产生候选集的,采用深度优先的挖掘方式,一步一步进行挖掘。gSpan算法可以进行对于化学分子的结构挖掘。

时间: 2024-11-06 01:34:35

gSpan频繁子图挖掘算法的相关文章

静态频繁子图挖掘算法用于动态网络——gSpan算法研究

摘要 随着信息技术的不断发展,人类可以很容易地收集和储存大量的数据,然而,如何在海量的数据中提取对用户有用的信息逐渐地成为巨大挑战.为了应对这种挑战,数据挖掘技术应运而生,成为了最近一段时期数据科学的和人工智能领域内的研究热点.数据集中的频繁模式作为一种有价值的信息,受到了人们的广泛关注,成为了数据挖掘技术研究领域内的热门话题和研究重点. 传统的频繁模式挖掘技术被用来在事务数据集中发现频繁项集,然而随着数据挖掘技术应用到非传统领域,单纯的事务数据结构很难对新的领域的数据进行有效的建模.因此,频繁

【甘道夫】并行化频繁模式挖掘算法FP Growth及其在Mahout下的命令使用

今天调研了并行化频繁模式挖掘算法PFP Growth及其在Mahout下的命令使用,简单记录下试验结果,供以后查阅: 环境:Jdk1.7 + Hadoop2.2.0单机伪集群 +  Mahout0.6(0.8和0.9版本都不包含该算法.Mahout0.6可以和Hadoop2.2.0和平共处有点意外orz) 部分输入数据,输入数据一行代表一个购物篮: 4750,19394,25651,6395,5592 26180,10895,24571,23295,20578,27791,2729,8637 7

高效频繁模式挖掘算法PrePost和FIN的C++源码

PrePost的C++源码见http://www.cis.pku.edu.cn/faculty/system/dengzhihong/Source%20Code/prepost.cpp. 算法内容参见论文:A New Algorithm for Fast Mining Frequent Itemsets Using N-Lists) 论文免费下载地址:http://info.scichina.com:8084/sciFe/EN/abstract/abstract508369.shtml  或ht

频繁项挖掘算法Apriori和FGrowth

一:背景介绍 最近在公司用spark的平台做了一个购物车的推荐,用到的算法主要是FGrowth算法,它是Apriori算法的升级版,算法的主要目的是找出频繁进行一起购买的商品.本文主要介绍两个算法的背景,触及到公司的推荐具体流程,这里就不介绍了. 二:Apriori Apriori算法是挖掘频繁项的基础算法,通过挖掘用户购买订单,发现频繁一起购买的商品集合.它采用一种逐层搜索的迭代方法,用k项集来搜索(k+1)项集.首先通过扫描数据中的订单,累计每项的计数,收集满足最小支持度的项,找出1项集的集

技术文章 | 频繁项集挖掘算法之FPGrowth

频繁项集挖掘算法用于挖掘经常一起出现的item集合(称为频繁项集),通过挖掘出这些频繁项集,当在一个事务中出现频繁项集的其中一个item,则可以把该频繁项集的其他item作为推荐. 比如经典的购物篮分析中啤酒.尿布故事,啤酒和尿布经常在用户的购物篮中一起出现,通过挖掘出啤酒.尿布这个啤酒项集,则当一个用户买了啤酒的时候可以为他推荐尿布,这样用户购买的可能性会比较大,从而达到组合营销的目的. 常见的频繁项集挖掘算法有两类,一类是Apriori算法,另一类是FPGrowth.Apriori通过不断的

PrefixSpan序列模式挖掘算法

更多数据挖掘代码:https://github.com/linyiqun/DataMiningAlgorithm 介绍 与GSP一样,PrefixSpan算法也是序列模式分析算法的一种,不过与前者不同的是PrefixSpan算法不产生任何的侯选集,在这点上可以说已经比GSP好很多了.PrefixSpan算法可以挖掘出满足阈值的所有序列模式,可以说是非常经典的算法.序列的格式就是上文中提到过的类似于<a, b, (de)>这种的. 算法原理 PrefixSpan算法的原理是采用后缀序列转前缀序列

经典的观点挖掘算法(文本挖掘系列)

最近阅读了一篇关于观点挖掘的KDD论文(Mining and Summarizing Customer Reviews,KDD04),其挖掘算法很经典,特此做记录. 该论文要解决的问题是,识别用户评论的情感(positive or negative),并作归纳,为用户购买产品提供真实有效的参考.归纳的形式如下(以数码相机为例): 数码相机: 特征: 照片质量 Positive:    253 <用户评论的句子> Negative:    8 <用户评论的句子> 特征: 大小 Pos

关联规则挖掘算法AFPIM

(参考文献来自An Efficient Approach for Maintaining Association Rules  based on Adjusting FP-tree Structure Jia-Ling Koh and Shui-Feng Shieh  Department of Information and Computer Education 其中有大量的删减,如果想直奔主题,看干货,可直接从3.调整FP_tree的策略 开始看起@OUYM) 1.Introduction

关联规则挖掘算法综述

摘  要  本文介绍了关联规则的基本概念和分类方法,列举了一些关联规则挖掘算法并简要分析了典型算法,展望了关联规则挖掘的未来研究方向. 关键词  数据挖掘,关联规则,频集,Apriori算法,FP-树 1 引言 关联规则挖掘发现大量数据中项集之间有趣的关联或相关联系.它在数据挖掘中是一个重要的课题,最近几年已被业界所广泛研究. 关联规则挖掘的一个典型例子是购物篮分析.关联规则研究有助于发现交易数据库中不同商品(项)之间的联系,找出顾客购买行为模式,如购买了某一商品对购买其他商品的影响.分析结果可