数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(下)

本文接数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(上).

(update 2012.12.28 关于本项目下载及运行的常见问题 FAQ见 newsgroup18828文本分类器、文本聚类器、关联分析频繁模式挖掘算法的Java实现工程下载及运行FAQ )

本文要点如下:

介绍基于LSI(隐性语义索引)中SVD分解做特征降维的方法

介绍两外两种文本聚类算法MBSAS算法及DBSCAN算法

对比三种算法对newsgroup18828文档集的聚类效果

1、SVD分解降维

    以词项(terms)为行, 文档(documents)为列做一个大矩阵(matrix). 设一共有t行d列, 矩阵名为A. 矩阵的元素为词项的tf-idf值。然后对该矩阵做SVD分解  A=T*S*D‘,把S的m个对角元素的前k个保留(最大的k个保留), 后m-k个置0, 我们可以得到一个新的近似的分解: Xhat=T*S*D’ 。Xhat在最小二乘意义下是X的最佳近似

给定矩阵A, 基于A可以问三类同文件检索密切有关的问题
术语i和j有多相似? 
即术语的类比和聚类问题
文件i和j有多相似?
即文件的类比和聚类问题
术语i和文件j有多相关?
即术语和文件的关联问题

利用SVD分解得到的矩阵可以计算这三个问题,方法如下(DT代表D的转置,以此类推)

比较两个术语  
做"正向"乘法:
Xhat*XhatT=T*S*DT*D*S*TT=T*S2*TT=(TS)*(TS)T
DT*D=I, 因为D已经是正交归一的 ,s=sT
它的第i行第j列表明了术语i和j的相似程度

比较两个文件做"逆向"乘法:
XhatT*Xhat=D*S*TT*T*S*DT=D*S2*DT=(DS)(DS)T
TT*T=I, 因为T已经是正交归一的, s=sT
它的第i行第j列表明了文件i和j的相似程度
此法给出了求文件之间相似度的一个途径,于是可以基于此相似度矩阵实现K-means算法

比较一个文件和一个术语恰巧就是Xhat本身. 
它的第i行第j列表明了术语i和文件j的相关联程度.

SVD分解主要基于JAMA矩阵运算包实现,JAMA矩阵运算包下载见http://math.nist.gov/javanumerics/jama/

DimensionReduction.java

[cpp]  view plain copy
  1. package com.pku.yangliu;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.Iterator;  
  5. import java.util.Map;  
  6. import java.util.Set;  
  7.   
  8. import Jama.Matrix;  
  9. import Jama.SingularValueDecomposition;  
  10.   
  11. /**基于LSI对文档的特征向量做降维,SVD运算基于JAMA矩阵运算包实现 
  12.  * 
  13.  */  
  14. public class DimensionReduction {  
  15.   
  16.     /**把测试样例的map转化成文档相似性矩阵 
  17.      * @param Map<String, Map<String, Double>> allTestSampleMap 所有测试样例的<文件名,向量>构成的map 
  18.      * @param String[] terms 特征词集合 
  19.      * @return double[][] doc-doc相似性矩阵 
  20.      * @throws IOException  
  21.      */  
  22.     public double[][] getSimilarityMatrix(  
  23.             Map<String, Map<String, Double>> allTestSampleMap, String[] terms) {  
  24.         // TODO Auto-generated method stub  
  25.         System.out.println("Begin compute docTermMatrix!");  
  26.         int i = 0;  
  27.         double [][] docTermMatrix = new double[allTestSampleMap.size()][terms.length];  
  28.         Set<Map.Entry<String, Map<String,Double>>> allTestSampleMapSet = allTestSampleMap.entrySet();  
  29.         for(Iterator<Map.Entry<String, Map<String,Double>>> it = allTestSampleMapSet.iterator();it.hasNext();){  
  30.             Map.Entry<String, Map<String,Double>> me = it.next();     
  31.             for(int j = 0; j < terms.length; j++){  
  32.                 if(me.getValue().containsKey(terms[j])){  
  33.                     docTermMatrix[i][j] = me.getValue().get(terms[j]);  
  34.                 }  
  35.                 else {  
  36.                     docTermMatrix[i][j] =0;  
  37.                 }  
  38.             }  
  39.             i++;      
  40.         }  
  41.         double[][] similarityMatrix = couputeSimilarityMatrix(docTermMatrix);  
  42.         return similarityMatrix;  
  43.     }  
  44.   
  45.     /**基于docTermMatrix生成相似性矩阵 
  46.      * @param double[][] docTermMatrix doc-term矩阵 
  47.      * @return double[][] doc-doc相似性矩阵 
  48.      * @throws IOException  
  49.      */  
  50.     private double[][] couputeSimilarityMatrix(double[][] docTermMatrix) {  
  51.         // TODO Auto-generated method stub  
  52.         System.out.println("Compute docTermMatrix done! begin compute SVD");  
  53.         Matrix docTermM = new Matrix(docTermMatrix);  
  54.         SingularValueDecomposition s = docTermM.transpose().svd();  
  55.         System.out.println(" Compute SVD done!");  
  56.         //A*A' = D*S*S'*D'   如果是doc-term矩阵  
  57.         //A'*A = D*S'*S*D'   如果是term-doc矩阵  
  58.         //注意svd函数只适合行数大于列数的矩阵,如果行数小于列数,可对其转置矩阵做SVD分解  
  59.         Matrix D = s.getU();  
  60.         Matrix S = s.getS();  
  61.         for(int i = 100; i < S.getRowDimension(); i++){//降到100维  
  62.             S.set(i, i, 0);  
  63.         }  
  64.         System.out.println("Compute SimilarityMatrix done!");  
  65.         return D.times(S.transpose().times(S.times(D.transpose()))).getArray();  
  66.     }  
  67. }  
2、基于SVD分解降维的K-means算法

      有了上面得到的文档与文档之间的相似性矩阵后,我们就可以实现另一个版本的K-means算法了。注意中心点的计算是直接对该聚类中的所有文档的距离向量求平均,作为该中心点与其他所有文档的距离。具体实现如下,主函数在数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(上)中已经给出。

[java]  view plain copy
  1. package com.pku.yangliu;  
  2.   
  3. import java.io.FileWriter;  
  4. import java.io.IOException;  
  5. import java.util.Iterator;  
  6. import java.util.Map;  
  7. import java.util.Set;  
  8. import java.util.TreeMap;  
  9. import java.util.Vector;  
  10. import java.lang.Integer;  
  11.   
  12.   
  13. /**Kmeans聚类算法的实现类,将newsgroups文档集聚成10类、20类、30类,采用SVD分解 
  14.  * 算法结束条件:当每个点最近的聚类中心点就是它所属的聚类中心点时,算法结束 
  15.  * 
  16.  */  
  17.   
  18. public class KmeansSVDCluster {  
  19.       
  20.     /**Kmeans算法主过程 
  21.      * @param Map<String, Map<String, Double>> allTestSampleMap 所有测试样例的<文件名,向量>构成的map 
  22.      * @param double [][] docSimilarityMatrix 文档与文档的相似性矩阵 [i,j]为文档i与文档j的相似性度量 
  23.      * @param int K 聚类的数量 
  24.      * @return Map<String,Integer> 聚类的结果  即<文件名,聚类完成后所属的类别标号> 
  25.      * @throws IOException  
  26.      */  
  27.     private Map<String, Integer> doProcess(  
  28.             Map<String, Map<String, Double>> allTestSampleMap, double[][] docSimilarityMatrix, int K) {  
  29.         // TODO Auto-generated method stub  
  30.         //0、首先获取allTestSampleMap所有文件名顺序组成的数组  
  31.         String[] testSampleNames = new String[allTestSampleMap.size()];  
  32.         int count = 0, tsLength = allTestSampleMap.size();  
  33.         Set<Map.Entry<String, Map<String, Double>>> allTestSampeleMapSet = allTestSampleMap.entrySet();  
  34.         for(Iterator<Map.Entry<String, Map<String, Double>>> it = allTestSampeleMapSet.iterator(); it.hasNext(); ){  
  35.             Map.Entry<String, Map<String, Double>> me = it.next();  
  36.             testSampleNames[count++] = me.getKey();  
  37.         }  
  38.         //1、初始点的选择算法是随机选择或者是均匀分开选择,这里采用后者  
  39.         Map<Integer, double[]> meansMap = getInitPoint(testSampleNames, docSimilarityMatrix, K);//保存K个中心点  
  40.         //2、初始化K个聚类  
  41.         int [] assignMeans = new int[tsLength];//记录所有点属于的聚类序号,初始化全部为0  
  42.         Map<Integer, Vector<Integer>> clusterMember = new TreeMap<Integer,Vector<Integer>>();//记录每个聚类的成员点序号  
  43.         Vector<Integer> mem = new Vector<Integer>();  
  44.         int iterNum = 0;//迭代次数  
  45.         while(true){  
  46.             System.out.println("Iteration No." + (iterNum++) + "----------------------");  
  47.             //3、找出每个点最近的聚类中心  
  48.             int[] nearestMeans = new int[tsLength];  
  49.             for(int i = 0; i < tsLength; i++){  
  50.                 nearestMeans[i] = findNearestMeans(meansMap, i);  
  51.             }  
  52.             //4、判断当前所有点属于的聚类序号是否已经全部是其离得最近的聚类,如果是或者达到最大的迭代次数,那么结束算法  
  53.             int okCount = 0;  
  54.             for(int i = 0; i <tsLength; i++){  
  55.                 if(nearestMeans[i] == assignMeans[i]) okCount++;  
  56.             }  
  57.             System.out.println("okCount = " + okCount);  
  58.             if(okCount == tsLength || iterNum >= 25break;//最大迭代次数1000次  
  59.             //5、如果前面条件不满足,那么需要重新聚类再进行一次迭代,需要修改每个聚类的成员和每个点属于的聚类信息  
  60.             clusterMember.clear();  
  61.             for(int i = 0; i < tsLength; i++){  
  62.                 assignMeans[i] = nearestMeans[i];  
  63.                 if(clusterMember.containsKey(nearestMeans[i])){  
  64.                     clusterMember.get(nearestMeans[i]).add(i);    
  65.                 }  
  66.                 else {  
  67.                     mem.clear();  
  68.                     mem.add(i);  
  69.                     Vector<Integer> tempMem = new Vector<Integer>();  
  70.                     tempMem.addAll(mem);  
  71.                     clusterMember.put(nearestMeans[i], tempMem);  
  72.                 }  
  73.             }  
  74.             //6、重新计算每个聚类的中心点  
  75.             for(int i = 0; i < K; i++){  
  76.                 if(!clusterMember.containsKey(i)){//注意kmeans可能产生空聚类  
  77.                     continue;  
  78.                 }  
  79.                 double[] newMean = computeNewMean(clusterMember.get(i), docSimilarityMatrix);  
  80.                 meansMap.put(i, newMean);  
  81.             }  
  82.         }  
  83.           
  84.         //7、形成聚类结果并且返回  
  85.         Map<String, Integer> resMap = new TreeMap<String, Integer>();  
  86.         for(int i = 0; i < tsLength; i++){  
  87.             resMap.put(testSampleNames[i], assignMeans[i]);  
  88.         }  
  89.         return resMap;  
  90.     }  
  91.   
  92.     /**计算新的聚类中心与每个文档的相似度 
  93.      * @param clusterM 该聚类包含的所有文档的序号 
  94.      * @param double [][] docSimilarityMatrix 文档之间的相似度矩阵 
  95.      * @return double[] 新的聚类中心与每个文档的相似度 
  96.      * @throws IOException  
  97.      */  
  98.     private double[] computeNewMean(Vector<Integer> clusterM,  
  99.             double [][] docSimilarityMatrix) {  
  100.         // TODO Auto-generated method stub  
  101.         double sim;  
  102.         double [] newMean = new double[docSimilarityMatrix.length];  
  103.         double memberNum = (double)clusterM.size();  
  104.         for(int i = 0; i < docSimilarityMatrix.length; i++){  
  105.             sim = 0;  
  106.             for(Iterator<Integer> it = clusterM.iterator(); it.hasNext();){  
  107.                 sim += docSimilarityMatrix[it.next()][i];  
  108.             }  
  109.             newMean[i] = sim / memberNum;  
  110.         }  
  111.         return newMean;  
  112.     }  
  113.   
  114.     /**找出距离当前点最近的聚类中心 
  115.      * @param Map<Integer, double[]> meansMap 中心点Map value为中心点和每个文档的相似度 
  116.      * @param int m 
  117.      * @return i 最近的聚类中心的序 号 
  118.      * @throws IOException  
  119.      */  
  120.     private int findNearestMeans(Map<Integer, double[]> meansMap ,int m) {  
  121.         // TODO Auto-generated method stub  
  122.         double maxSim = 0;  
  123.         int j = -1;  
  124.         double[] simArray;  
  125.         Set<Map.Entry<Integer, double[]>> meansMapSet = meansMap.entrySet();  
  126.         for(Iterator<Map.Entry<Integer, double[]>> it = meansMapSet.iterator(); it.hasNext();){  
  127.             Map.Entry<Integer, double[]> me = it.next();  
  128.             simArray = me.getValue();  
  129.             if(maxSim < simArray[m]){  
  130.                 maxSim = simArray[m];  
  131.                 j = me.getKey();  
  132.             }  
  133.         }  
  134.         return j;  
  135.     }  
  136.   
  137.     /**获取kmeans算法迭代的初始点 
  138.      * @param k 聚类的数量 
  139.      * @param String[] testSampleNames 测试样例文件名数组 
  140.      * @param double[][] docSimilarityMatrix 文档相似性矩阵 
  141.      * @return Map<Integer, double[]> 初始中心点容器 key是类标号,value为该类与其他文档的相似度数组 
  142.      * @throws IOException  
  143.      */  
  144.     private Map<Integer, double[]> getInitPoint(String[] testSampleNames, double[][] docSimilarityMatrix, int K) {  
  145.         // TODO Auto-generated method stub  
  146.         int i = 0;  
  147.         Map<Integer, double[]> meansMap = new TreeMap<Integer, double[]>();//保存K个聚类中心点向量  
  148.         System.out.println("本次聚类的初始点对应的文件为:");  
  149.         for(int count = 0; count < testSampleNames.length; count++){  
  150.             if(count == i * testSampleNames.length / K){  
  151.                 meansMap.put(i, docSimilarityMatrix[count]);  
  152.                 System.out.println(testSampleNames[count]);  
  153.                 i++;  
  154.             }  
  155.         }  
  156.         return meansMap;  
  157.     }  
  158.   
  159.     /**输出聚类结果到文件中 
  160.      * @param kmeansClusterResultFile 输出文件目录 
  161.      * @param kmeansClusterResult 聚类结果 
  162.      * @throws IOException  
  163.      */  
  164.     private void printClusterResult(Map<String, Integer> kmeansClusterResult, String kmeansClusterResultFile) throws IOException {  
  165.         // TODO Auto-generated method stub  
  166.         FileWriter resWriter = new FileWriter(kmeansClusterResultFile);  
  167.         Set<Map.Entry<String,Integer>> kmeansClusterResultSet = kmeansClusterResult.entrySet();  
  168.         for(Iterator<Map.Entry<String,Integer>> it = kmeansClusterResultSet.iterator(); it.hasNext(); ){  
  169.             Map.Entry<String, Integer> me = it.next();  
  170.             resWriter.append(me.getKey() + " " + me.getValue() + "\n");  
  171.         }  
  172.         resWriter.flush();  
  173.         resWriter.close();  
  174.     }  
  175.       
  176.     /**Kmeans算法 
  177.      * @param String testSampleDir 测试样例目录 
  178.      * @param String[] term 特征词数组 
  179.      * @throws IOException  
  180.      */  
  181.     public void KmeansClusterMain(String testSampleDir, String[] terms) throws IOException {  
  182.         //首先计算文档TF-IDF向量,保存为Map<String,Map<String,Double>> 即为Map<文件名,Map<特征词,TF-IDF值>>  
  183.         ComputeWordsVector computeV = new ComputeWordsVector();  
  184.         DimensionReduction dimReduce = new DimensionReduction();  
  185.         int[] K = {102030};  
  186.         Map<String,Map<String,Double>> allTestSampleMap = computeV.computeTFMultiIDF(testSampleDir);  
  187.         //基于allTestSampleMap生成一个doc*term矩阵,然后做SVD分解  
  188.         double[][] docSimilarityMatrix = dimReduce.getSimilarityMatrix(allTestSampleMap, terms);  
  189.         for(int i = 0; i < K.length; i++){  
  190.             System.out.println("开始聚类,聚成" + K[i] + "类");  
  191.             String KmeansClusterResultFile = "F:/DataMiningSample/KmeansClusterResult/";  
  192.             Map<String,Integer> KmeansClusterResult = new TreeMap<String, Integer>();  
  193.             KmeansClusterResult = doProcess(allTestSampleMap, docSimilarityMatrix, K[i]);  
  194.             KmeansClusterResultFile += K[i];  
  195.             printClusterResult(KmeansClusterResult,KmeansClusterResultFile);  
  196.             System.out.println("The Entropy for this Cluster is " + computeV.evaluateClusterRes(KmeansClusterResultFile, K[i]));  
  197.         }  
  198.     }  
  199. }  
3、K-means算法、MBSAS算法、DBSCAN算法三种算法的聚类结果对比

     另外两种聚类算法MBSAS算法和DBSCAN算法由我们组另外两位同学实现,其实也很简单,源码这里就不贴出来了。感兴趣的朋友可以到点击打开链接下载eclipse工程运行。这三种算法的聚类结果采用熵值大小来评价,熵值越小聚类效果越好,具体如下

数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(下)_第1张图片

数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(下)_第2张图片

数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(下)_第3张图片

数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(下)_第4张图片

数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(下)_第5张图片

可见对newsgroup文档集聚类采用K-means算法,用余弦相似度或者内积度量相似度可以达到良好的效果。而SVD分解还是很耗时间,事实上对20000X3000的矩阵做SVD分解的时间慢得难以忍受,我还尝试对小规模数据聚类,但是发现降维后聚类结果熵值超过了2,不及DF法降维的聚类效果。因此对于文本聚类的SVD降维未必是好方法,。除了这三种聚类算法,还有层次聚类算法等其他很多算法,以后会尝试给出其他算法的实现和聚类效果对比。敬请关注:)

你可能感兴趣的:(数据挖掘-基于Kmeans算法、MBSAS算法及DBSCAN算法的newsgroup18828文本聚类器的JAVA实现(下))