zoukankan      html  css  js  c++  java
  • 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算法可以进行对于化学分子的结构挖掘。

  • 相关阅读:
    某个牛人做WINDOWS系统文件详解
    常用ASP脚本程序集锦
    LINUX基础:文件安全与权限
    proftpd+mysql+quota
    apache2.0.49tomcat5.0.19jk2建立virtualHost
    URL Redirection(转) Anny
    顶级域名后缀列表(转) Anny
    \u4E00\u9FA5意义 Anny
    How to POST Form Data Using Ruby(转) Anny
    How to get rid of 'Enter password to unlock your login keyring' in Ubuntu(转) Anny
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183992.html
Copyright © 2011-2022 走看看