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:

[java]  view plain copy print ?
  1. package DataMining_GSpan;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileReader;  
  6. import java.io.IOException;  
  7. import java.text.MessageFormat;  
  8. import java.util.ArrayList;  
  9. import java.util.HashMap;  
  10. import java.util.Map;  
  11.   
  12. /** 
  13.  * gSpan频繁子图挖掘算法工具类 
  14.  *  
  15.  * @author lyq 
  16.  *  
  17.  */  
  18. public class GSpanTool {  
  19.     // 文件数据类型  
  20.     public final String INPUT_NEW_GRAPH = "t";  
  21.     public final String INPUT_VERTICE = "v";  
  22.     public final String INPUT_EDGE = "e";  
  23.     // Label标号的最大数量,包括点标号和边标号  
  24.     public final int LABEL_MAX = 100;  
  25.   
  26.     // 测试数据文件地址  
  27.     private String filePath;  
  28.     // 最小支持度率  
  29.     private double minSupportRate;  
  30.     // 最小支持度数,通过图总数与最小支持度率的乘积计算所得  
  31.     private int minSupportCount;  
  32.     // 初始所有图的数据  
  33.     private ArrayList<GraphData> totalGraphDatas;  
  34.     // 所有的图结构数据  
  35.     private ArrayList<Graph> totalGraphs;  
  36.     // 挖掘出的频繁子图  
  37.     private ArrayList<Graph> resultGraphs;  
  38.     // 边的频度统计  
  39.     private EdgeFrequency ef;  
  40.     // 节点的频度  
  41.     private int[] freqNodeLabel;  
  42.     // 边的频度  
  43.     private int[] freqEdgeLabel;  
  44.     // 重新标号之后的点的标号数  
  45.     private int newNodeLabelNum = 0;  
  46.     // 重新标号后的边的标号数  
  47.     private int newEdgeLabelNum = 0;  
  48.   
  49.     public GSpanTool(String filePath, double minSupportRate) {  
  50.         this.filePath = filePath;  
  51.         this.minSupportRate = minSupportRate;  
  52.         readDataFile();  
  53.     }  
  54.   
  55.     /** 
  56.      * 从文件中读取数据 
  57.      */  
  58.     private void readDataFile() {  
  59.         File file = new File(filePath);  
  60.         ArrayList<String[]> dataArray = new ArrayList<String[]>();  
  61.   
  62.         try {  
  63.             BufferedReader in = new BufferedReader(new FileReader(file));  
  64.             String str;  
  65.             String[] tempArray;  
  66.             while ((str = in.readLine()) != null) {  
  67.                 tempArray = str.split(" ");  
  68.                 dataArray.add(tempArray);  
  69.             }  
  70.             in.close();  
  71.         } catch (IOException e) {  
  72.             e.getStackTrace();  
  73.         }  
  74.   
  75.         calFrequentAndRemove(dataArray);  
  76.     }  
  77.   
  78.     /** 
  79.      * 统计边和点的频度,并移除不频繁的点边,以标号作为统计的变量 
  80.      *  
  81.      * @param dataArray 
  82.      *            原始数据 
  83.      */  
  84.     private void calFrequentAndRemove(ArrayList<String[]> dataArray) {  
  85.         int tempCount = 0;  
  86.         freqNodeLabel = new int[LABEL_MAX];  
  87.         freqEdgeLabel = new int[LABEL_MAX];  
  88.   
  89.         // 做初始化操作  
  90.         for (int i = 0; i < LABEL_MAX; i++) {  
  91.             // 代表标号为i的节点目前的数量为0  
  92.             freqNodeLabel[i] = 0;  
  93.             freqEdgeLabel[i] = 0;  
  94.         }  
  95.   
  96.         GraphData gd = null;  
  97.         totalGraphDatas = new ArrayList<>();  
  98.         for (String[] array : dataArray) {  
  99.             if (array[0].equals(INPUT_NEW_GRAPH)) {  
  100.                 if (gd != null) {  
  101.                     totalGraphDatas.add(gd);  
  102.                 }  
  103.   
  104.                 // 新建图  
  105.                 gd = new GraphData();  
  106.             } else if (array[0].equals(INPUT_VERTICE)) {  
  107.                 // 每个图中的每种图只统计一次  
  108.                 if (!gd.getNodeLabels().contains(Integer.parseInt(array[2]))) {  
  109.                     tempCount = freqNodeLabel[Integer.parseInt(array[2])];  
  110.                     tempCount++;  
  111.                     freqNodeLabel[Integer.parseInt(array[2])] = tempCount;  
  112.                 }  
  113.   
  114.                 gd.getNodeLabels().add(Integer.parseInt(array[2]));  
  115.                 gd.getNodeVisibles().add(true);  
  116.             } else if (array[0].equals(INPUT_EDGE)) {  
  117.                 // 每个图中的每种图只统计一次  
  118.                 if (!gd.getEdgeLabels().contains(Integer.parseInt(array[3]))) {  
  119.                     tempCount = freqEdgeLabel[Integer.parseInt(array[3])];  
  120.                     tempCount++;  
  121.                     freqEdgeLabel[Integer.parseInt(array[3])] = tempCount;  
  122.                 }  
  123.   
  124.                 int i = Integer.parseInt(array[1]);  
  125.                 int j = Integer.parseInt(array[2]);  
  126.   
  127.                 gd.getEdgeLabels().add(Integer.parseInt(array[3]));  
  128.                 gd.getEdgeX().add(i);  
  129.                 gd.getEdgeY().add(j);  
  130.                 gd.getEdgeVisibles().add(true);  
  131.             }  
  132.         }  
  133.         // 把最后一块gd数据加入  
  134.         totalGraphDatas.add(gd);  
  135.         minSupportCount = (int) (minSupportRate * totalGraphDatas.size());  
  136.   
  137.         for (GraphData g : totalGraphDatas) {  
  138.             g.removeInFreqNodeAndEdge(freqNodeLabel, freqEdgeLabel,  
  139.                     minSupportCount);  
  140.         }  
  141.     }  
  142.   
  143.     /** 
  144.      * 根据标号频繁度进行排序并且重新标号 
  145.      */  
  146.     private void sortAndReLabel() {  
  147.         int label1 = 0;  
  148.         int label2 = 0;  
  149.         int temp = 0;  
  150.         // 点排序名次  
  151.         int[] rankNodeLabels = new int[LABEL_MAX];  
  152.         // 边排序名次  
  153.         int[] rankEdgeLabels = new int[LABEL_MAX];  
  154.         // 标号对应排名  
  155.         int[] nodeLabel2Rank = new int[LABEL_MAX];  
  156.         int[] edgeLabel2Rank = new int[LABEL_MAX];  
  157.   
  158.         for (int i = 0; i < LABEL_MAX; i++) {  
  159.             // 表示排名第i位的标号为i,[i]中的i表示排名  
  160.             rankNodeLabels[i] = i;  
  161.             rankEdgeLabels[i] = i;  
  162.         }  
  163.   
  164.         for (int i = 0; i < freqNodeLabel.length - 1; i++) {  
  165.             int k = 0;  
  166.             label1 = rankNodeLabels[i];  
  167.             temp = label1;  
  168.             for (int j = i + 1; j < freqNodeLabel.length; j++) {  
  169.                 label2 = rankNodeLabels[j];  
  170.   
  171.                 if (freqNodeLabel[temp] < freqNodeLabel[label2]) {  
  172.                     // 进行标号的互换  
  173.                     temp = label2;  
  174.                     k = j;  
  175.                 }  
  176.             }  
  177.   
  178.             if (temp != label1) {  
  179.                 // 进行i,k排名下的标号对调  
  180.                 temp = rankNodeLabels[k];  
  181.                 rankNodeLabels[k] = rankNodeLabels[i];  
  182.                 rankNodeLabels[i] = temp;  
  183.             }  
  184.         }  
  185.   
  186.         // 对边同样进行排序  
  187.         for (int i = 0; i < freqEdgeLabel.length - 1; i++) {  
  188.             int k = 0;  
  189.             label1 = rankEdgeLabels[i];  
  190.             temp = label1;  
  191.             for (int j = i + 1; j < freqEdgeLabel.length; j++) {  
  192.                 label2 = rankEdgeLabels[j];  
  193.   
  194.                 if (freqEdgeLabel[temp] < freqEdgeLabel[label2]) {  
  195.                     // 进行标号的互换  
  196.                     temp = label2;  
  197.                     k = j;  
  198.                 }  
  199.             }  
  200.   
  201.             if (temp != label1) {  
  202.                 // 进行i,k排名下的标号对调  
  203.                 temp = rankEdgeLabels[k];  
  204.                 rankEdgeLabels[k] = rankEdgeLabels[i];  
  205.                 rankEdgeLabels[i] = temp;  
  206.             }  
  207.         }  
  208.   
  209.         // 将排名对标号转为标号对排名  
  210.         for (int i = 0; i < rankNodeLabels.length; i++) {  
  211.             nodeLabel2Rank[rankNodeLabels[i]] = i;  
  212.         }  
  213.   
  214.         for (int i = 0; i < rankEdgeLabels.length; i++) {  
  215.             edgeLabel2Rank[rankEdgeLabels[i]] = i;  
  216.         }  
  217.   
  218.         for (GraphData gd : totalGraphDatas) {  
  219.             gd.reLabelByRank(nodeLabel2Rank, edgeLabel2Rank);  
  220.         }  
  221.   
  222.         // 根据排名找出小于支持度值的最大排名值  
  223.         for (int i = 0; i < rankNodeLabels.length; i++) {  
  224.             if (freqNodeLabel[rankNodeLabels[i]] > minSupportCount) {  
  225.                 newNodeLabelNum = i;  
  226.             }  
  227.         }  
  228.         for (int i = 0; i < rankEdgeLabels.length; i++) {  
  229.             if (freqEdgeLabel[rankEdgeLabels[i]] > minSupportCount) {  
  230.                 newEdgeLabelNum = i;  
  231.             }  
  232.         }  
  233.         //排名号比数量少1,所以要加回来  
  234.         newNodeLabelNum++;  
  235.         newEdgeLabelNum++;  
  236.     }  
  237.   
  238.     /** 
  239.      * 进行频繁子图的挖掘 
  240.      */  
  241.     public void freqGraphMining() {  
  242.         long startTime =  System.currentTimeMillis();  
  243.         long endTime = 0;  
  244.         Graph g;  
  245.         sortAndReLabel();  
  246.   
  247.         resultGraphs = new ArrayList<>();  
  248.         totalGraphs = new ArrayList<>();  
  249.         // 通过图数据构造图结构  
  250.         for (GraphData gd : totalGraphDatas) {  
  251.             g = new Graph();  
  252.             g = g.constructGraph(gd);  
  253.             totalGraphs.add(g);  
  254.         }  
  255.   
  256.         // 根据新的点边的标号数初始化边频繁度对象  
  257.         ef = new EdgeFrequency(newNodeLabelNum, newEdgeLabelNum);  
  258.         for (int i = 0; i < newNodeLabelNum; i++) {  
  259.             for (int j = 0; j < newEdgeLabelNum; j++) {  
  260.                 for (int k = 0; k < newNodeLabelNum; k++) {  
  261.                     for (Graph tempG : totalGraphs) {  
  262.                         if (tempG.hasEdge(i, j, k)) {  
  263.                             ef.edgeFreqCount[i][j][k]++;  
  264.                         }  
  265.                     }  
  266.                 }  
  267.             }  
  268.         }  
  269.   
  270.         Edge edge;  
  271.         GraphCode gc;  
  272.         for (int i = 0; i < newNodeLabelNum; i++) {  
  273.             for (int j = 0; j < newEdgeLabelNum; j++) {  
  274.                 for (int k = 0; k < newNodeLabelNum; k++) {  
  275.                     if (ef.edgeFreqCount[i][j][k] >= minSupportCount) {  
  276.                         gc = new GraphCode();  
  277.                         edge = new Edge(01, i, j, k);  
  278.                         gc.getEdgeSeq().add(edge);  
  279.   
  280.                         // 将含有此边的图id加入到gc中  
  281.                         for (int y = 0; y < totalGraphs.size(); y++) {  
  282.                             if (totalGraphs.get(y).hasEdge(i, j, k)) {  
  283.                                 gc.getGs().add(y);  
  284.                             }  
  285.                         }  
  286.                         // 对某条满足阈值的边进行挖掘  
  287.                         subMining(gc, 2);  
  288.                     }  
  289.                 }  
  290.             }  
  291.         }  
  292.           
  293.         endTime = System.currentTimeMillis();  
  294.         System.out.println("算法执行时间"+ (endTime-startTime) + "ms");  
  295.         printResultGraphInfo();  
  296.     }  
  297.   
  298.     /** 
  299.      * 进行频繁子图的挖掘 
  300.      *  
  301.      * @param gc 
  302.      *            图编码 
  303.      * @param next 
  304.      *            图所含的点的个数 
  305.      */  
  306.     public void subMining(GraphCode gc, int next) {  
  307.         Edge e;  
  308.         Graph graph = new Graph();  
  309.         int id1;  
  310.         int id2;  
  311.   
  312.         for(int i=0; i<next; i++){  
  313.             graph.nodeLabels.add(-1);  
  314.             graph.edgeLabels.add(new ArrayList<Integer>());  
  315.             graph.edgeNexts.add(new ArrayList<Integer>());  
  316.         }  
  317.   
  318.         // 首先根据图编码中的边五元组构造图  
  319.         for (int i = 0; i < gc.getEdgeSeq().size(); i++) {  
  320.             e = gc.getEdgeSeq().get(i);  
  321.             id1 = e.ix;  
  322.             id2 = e.iy;  
  323.   
  324.             graph.nodeLabels.set(id1, e.x);  
  325.             graph.nodeLabels.set(id2, e.y);  
  326.             graph.edgeLabels.get(id1).add(e.a);  
  327.             graph.edgeLabels.get(id2).add(e.a);  
  328.             graph.edgeNexts.get(id1).add(id2);  
  329.             graph.edgeNexts.get(id2).add(id1);  
  330.         }  
  331.   
  332.         DFSCodeTraveler dTraveler = new DFSCodeTraveler(gc.getEdgeSeq(), graph);  
  333.         dTraveler.traveler();  
  334.         if (!dTraveler.isMin) {  
  335.             return;  
  336.         }  
  337.   
  338.         // 如果当前是最小编码则将此图加入到结果集中  
  339.         resultGraphs.add(graph);  
  340.         Edge e1;  
  341.         ArrayList<Integer> gIds;  
  342.         SubChildTraveler sct;  
  343.         ArrayList<Edge> edgeArray;  
  344.         // 添加潜在的孩子边,每条孩子边所属的图id  
  345.         HashMap<Edge, ArrayList<Integer>> edge2GId = new HashMap<>();  
  346.         for (int i = 0; i < gc.gs.size(); i++) {  
  347.             int id = gc.gs.get(i);  
  348.   
  349.             // 在此结构的条件下,在多加一条边构成子图继续挖掘  
  350.             sct = new SubChildTraveler(gc.edgeSeq, totalGraphs.get(id));  
  351.             sct.traveler();  
  352.             edgeArray = sct.getResultChildEdge();  
  353.   
  354.             // 做边id的更新  
  355.             for (Edge e2 : edgeArray) {  
  356.                 if (!edge2GId.containsKey(e2)) {  
  357.                     gIds = new ArrayList<>();  
  358.                 } else {  
  359.                     gIds = edge2GId.get(e2);  
  360.                 }  
  361.   
  362.                 gIds.add(id);  
  363.                 edge2GId.put(e2, gIds);  
  364.             }  
  365.         }  
  366.   
  367.         for (Map.Entry entry : edge2GId.entrySet()) {  
  368.             e1 = (Edge) entry.getKey();  
  369.             gIds = (ArrayList<Integer>) entry.getValue();  
  370.   
  371.             // 如果此边的频度大于最小支持度值,则继续挖掘  
  372.             if (gIds.size() < minSupportCount) {  
  373.                 continue;  
  374.             }  
  375.   
  376.             GraphCode nGc = new GraphCode();  
  377.             nGc.edgeSeq.addAll(gc.edgeSeq);  
  378.             // 在当前图中新加入一条边,构成新的子图进行挖掘  
  379.             nGc.edgeSeq.add(e1);  
  380.             nGc.gs.addAll(gIds);  
  381.   
  382.             if (e1.iy == next) {  
  383.                 // 如果边的点id设置是为当前最大值的时候,则开始寻找下一个点  
  384.                 subMining(nGc, next + 1);  
  385.             } else {  
  386.                 // 如果此点已经存在,则next值不变  
  387.                 subMining(nGc, next);  
  388.             }  
  389.         }  
  390.     }  
  391.       
  392.     /** 
  393.      * 输出频繁子图结果信息 
  394.      */  
  395.     public void printResultGraphInfo(){  
  396.         System.out.println(MessageFormat.format("挖掘出的频繁子图的个数为:{0}个", resultGraphs.size()));  
  397.     }  
  398.   
  399. }  
这个算法在后来的实现时,渐渐的发现此算法的难度大大超出我预先的设想,不仅仅是其中的抽象性,还在于测试的复杂性,对于测试数据的捏造,如果用的是真实数据测的话,数据量太大,自己造数据拿捏的也不是很准确。我最后也只是自己伪造了一个图的数据,挖掘了其中的一条边的情况。大致的走了一个过程。代码并不算是完整的,仅供学习。

算法的缺点

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

算法的体会

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

算法的特点

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

你可能感兴趣的:(gSpan频繁子图挖掘算法)