[关闭]
@HUST-SuWB 2015-12-24T07:40:21.000000Z 字数 8371 阅读 322

基于Mahout的聚类实例

项目实战


基本概念

聚类分析是由若干模式组成的,通常,模式是一个度量的向量,或者是多维空间中的一个点。聚类分析以相似性为基础,在一个聚类中的模式之间比不在同一聚类中的模式之间具有更多的相似性。

需求分析

我的需求是分析全国各大高校在社科方向上的相似性,因此会以高校的若干维度为基础做聚类,最后在同一簇中的高校就定义为具有相似性。
具体由于数据限制的原因,我最后选择了15个维度的数据,如下:

维度 简介 权值
是否部属高校 - 是:500/否:100
讲师数 本校013/讲师的数量 直接取具体数值为权值
副教授数 本校012/副教授的数量 直接取具体数值为权值
教授数 本校011/副教授的数量 直接取具体数值为权值
性质类别 如01/综合大学 取类别编号*100作为权值,为了扩大非常见类别的影响力,如10/体育院校,这时权值为1000,可以显著得与权值为100的综合大学区别开
经济学 本校05-15年在本学科下的申报数量 直接取具体数值为权值
管理学 本校05-15年在本学科下的申报数量 直接取具体数值为权值
交叉学科 本校05-15年在本学科下的申报数量 直接取具体数值为权值
教育学 本校05-15年在本学科下的申报数量 直接取具体数值为权值
语言学 本校05-15年在本学科下的申报数量 直接取具体数值为权值
法学 本校05-15年本在学科下的申报数量 直接取具体数值为权值
艺术学 本校05-15年在本学科下的申报数量 直接取具体数值为权值
马克思主义/思想政治教育 本校05-15年在本学科下的申报数量 直接取具体数值为权值
中国文学 本校05-15年在本学科下的申报数量 直接取具体数值为权值
历史学 本校05-15年在本学科下的申报数量 直接取具体数值为权值

P.S. 上述选择的10个学科是社科类别下最热门的10个学科,因此做为样本指代所有学科的情况。

代码实例

  1. import java.io.BufferedReader;
  2. import java.io.IOException;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import org.apache.hadoop.conf.Configuration;
  6. import org.apache.hadoop.fs.FileSystem;
  7. import org.apache.hadoop.fs.Path;
  8. import org.apache.hadoop.io.IntWritable;
  9. import org.apache.hadoop.io.SequenceFile;
  10. import org.apache.hadoop.io.Text;
  11. import org.apache.mahout.clustering.classify.WeightedVectorWritable;
  12. import org.apache.mahout.clustering.kmeans.KMeansDriver;
  13. import org.apache.mahout.clustering.kmeans.Kluster;
  14. import org.apache.mahout.common.distance.EuclideanDistanceMeasure;
  15. import org.apache.mahout.math.DenseVector;
  16. import org.apache.mahout.math.NamedVector;
  17. import org.apache.mahout.math.Vector;
  18. import org.apache.mahout.math.VectorWritable;
  19. import tool.ClassifierHelper;
  20. import tool.CsvTool;
  21. import com.google.common.io.Closeables;
  22. /**
  23. * 聚类测试
  24. * 1,两个输入路径:一个是数据的点;一个是初始集群。
  25. 点的输入文件是SequenceFile(Key, VectorWritable)格式;
  26. 而初始集群的输入文件格式是SequenceFiles(Text, Cluster | Canopy)
  27. * 2,每次迭代会产生一个输出目录"cluster-N",输出文件格式为SequenceFile(Text, Cluster),表示第N次迭代后产生的clusters。
  28. * 3,输出目录"clusteredPoints",表示最终的集群结果,即每个集群中所包含的Points。
  29. * @author suwb
  30. * @since 2015-11-26
  31. */
  32. public class Clustering {
  33. private final String CSV_PATH = "F:\\source\\clustering\\部属高校_聚类.csv";
  34. private final String POINTS_PATH = "F:\\source\\clustering\\testdata\\points";
  35. private final String CLUSTERS_PATH = "F:\\source\\clustering\\testdata\\clusters";
  36. private final String OUTPUT_PATH = "F:\\source\\clustering\\output";
  37. //预处理数据(比如处理成csv)
  38. public void prepareData(){
  39. Dao dao = new Dao();
  40. List<String[]> dataList = new ArrayList<String[]>();
  41. List<Object[]> unitList = dao.queryBySql("select c_name from t_unit where c_type='部属高校'");//有效数据76个,可以分10簇
  42. for(Object[] unitName : unitList){
  43. String SQL = "--";
  44. try {
  45. Object[] thisObject = dao.queryBySql(SQL).get(0);
  46. String[] thisString = new String[16];
  47. thisString[0] = unitName[0].toString();
  48. int k=0;//跳出两层循环的标记
  49. for(int i=1; i<16; i++){
  50. if(i==3){//过滤异常数据
  51. if(Integer.parseInt(thisObject[i-1].toString())==0){
  52. k=1;
  53. continue;
  54. }
  55. }
  56. thisString[i] = thisObject[i-1].toString();
  57. }
  58. if(k==1){
  59. continue;
  60. }
  61. dataList.add(thisString);
  62. } catch (Exception e) {
  63. System.out.println(unitName[0].toString());
  64. }
  65. }
  66. String[] header = {"高校名", "是否部属高校", "高校类型", "讲师数量", "副教授数量", "教授数量", "经济学申报数量", "管理学申报数量", "交叉学科/综合研究申报数量",
  67. "教育学申报数量", "语言学申报数量", "法学申报数量", "艺术学申报数量", "马克思主义/思想政治教育申报数量", "中国文学申报数量", "历史学申报数量"};
  68. CsvTool.writeCsv(CSV_PATH, header, dataList);
  69. }
  70. //生成向量Vector
  71. public List<NamedVector> getVector() throws IOException{
  72. List<NamedVector> points = new ArrayList<NamedVector>();
  73. BufferedReader in = ClassifierHelper.open(CSV_PATH);//从输入的预测集文件读取数据
  74. //读取标题行,第一行变量名
  75. in.readLine();
  76. //读取下一行,数据行第一行
  77. String line = in.readLine();
  78. while (line != null) {
  79. //逐行向量化
  80. String[] univInfo = line.split(",");
  81. double[] fr = new double[univInfo.length-1];
  82. for(int i=0;i<univInfo.length-1;i++){
  83. fr[i] = Double.parseDouble(univInfo[i+1]);
  84. }
  85. Vector vector = new DenseVector(fr);
  86. NamedVector vec = new NamedVector(vector, univInfo[0]);
  87. points.add(vec);
  88. //读取下一行
  89. line = in.readLine();
  90. }
  91. Closeables.close(in, true);
  92. return points;
  93. }
  94. //生成SequenceFile格式的文件
  95. public void writePointsToFile(List<NamedVector> points, Path path, FileSystem fs, Configuration conf) throws IOException {
  96. SequenceFile.Writer writer = new SequenceFile.Writer(fs, conf,
  97. path, Text.class, VectorWritable.class);
  98. VectorWritable vec = new VectorWritable();
  99. for (NamedVector point : points) {
  100. vec.set(point);
  101. writer.append(new Text(point.getName()), vec);
  102. }
  103. writer.close();
  104. }
  105. //读SequenceFile文件
  106. public void readSequenceFile(Path path, FileSystem fs, Configuration conf) throws IOException{
  107. SequenceFile.Reader reader = new SequenceFile.Reader(fs, path, conf);
  108. Text key = new Text();
  109. VectorWritable value = new VectorWritable();
  110. while(reader.next(key, value)){
  111. System.out.println(key.toString() + " " + value.get().asFormatString());
  112. }
  113. reader.close();
  114. }
  115. //读cluster文件
  116. public void readCluster(Path path, FileSystem fs, Configuration conf) throws IOException{
  117. SequenceFile.Reader reader = new SequenceFile.Reader(fs, path, conf);
  118. IntWritable key = new IntWritable();
  119. WeightedVectorWritable value = new WeightedVectorWritable();
  120. while (reader.next(key, value)) {
  121. System.out.println(value.toString() + " belongs to cluster " + key.toString());
  122. }
  123. reader.close();
  124. }
  125. //写入初始中心点
  126. public void setCenterPoints(Path path, FileSystem fs, Configuration conf, int k, List<NamedVector> vectors) throws IOException{
  127. SequenceFile.Writer writer = new SequenceFile.Writer(fs, conf, path, Text.class, Kluster.class);
  128. for (int i = 0; i < k; i++) {
  129. Vector vec = vectors.get(i);
  130. Kluster cluster = new Kluster(vec, i, new EuclideanDistanceMeasure());
  131. writer.append(new Text(cluster.getIdentifier()), cluster);
  132. }
  133. writer.close();
  134. }
  135. public void work(Configuration conf) throws IOException, ClassNotFoundException, InterruptedException{
  136. Path inputPath = new Path(POINTS_PATH);
  137. Path clustersPath = new Path(CLUSTERS_PATH);
  138. Path outputPath = new Path(OUTPUT_PATH);
  139. // HadoopUtil.delete(conf, outputPath);
  140. // FuzzyKMeansDriver.run(conf, inputPath, clustersPath, outputPath, new TanimotoDistanceMeasure(), 0.001, 20, 2.0f, true, true, 0.0, true);
  141. KMeansDriver.run(conf, inputPath, clustersPath, outputPath, new EuclideanDistanceMeasure(), 0.001, 20, true, 0.0, true);
  142. System.out.println("Clusters are ready");
  143. }
  144. //执行聚类程序
  145. public void run() throws IOException, InterruptedException, ClassNotFoundException{
  146. // prepareData();
  147. Configuration conf = new Configuration();
  148. FileSystem fs = FileSystem.get(conf);
  149. // writePointsToFile(getVector(), new Path(POINTS_PATH + "\\file1"), fs, conf);
  150. setCenterPoints(new Path(CLUSTERS_PATH + "\\part-00000"), fs, conf, 10, getVector());
  151. work(conf);
  152. readCluster(new Path(OUTPUT_PATH + "\\" + Kluster.CLUSTERED_POINTS_DIR + "\\part-m-0"), fs, conf);
  153. }
  154. }
  1. package tool;
  2. import java.io.IOException;
  3. import java.nio.charset.Charset;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. import com.csvreader.CsvReader;
  7. import com.csvreader.CsvWriter;
  8. /**
  9. * CSV工具包
  10. * @author suwb
  11. */
  12. public class CsvTool {
  13. /**
  14. * 写CSV文件
  15. * @param outFilePath 数据文件路径
  16. * @param header 内容第一行标题
  17. * @param dataList 内容
  18. */
  19. public static void writeCsv(String outFilePath, String[] header, List<String[]> dataList) {
  20. CsvWriter writer = null;
  21. try {
  22. writer = new CsvWriter(outFilePath, ',', Charset.forName("UTF-8"));
  23. //写文件头
  24. if(header != null){
  25. writer.writeRecord(header);
  26. }
  27. //写文件内容
  28. for (String[] datas : dataList) {
  29. writer.writeRecord(datas);
  30. }
  31. } catch (IOException e) {
  32. e.printStackTrace();
  33. } finally {
  34. writer.close();
  35. }
  36. }
  37. /**
  38. * 读CSV文件
  39. * @param csvFilePath
  40. * @throws Exception
  41. */
  42. public static List<Object[]> readCsv(String csvFilePath) throws Exception {
  43. // 返回结果
  44. List<Object[]> datas = new ArrayList<Object[]>();
  45. CsvReader reader = new CsvReader(csvFilePath, ',', Charset.forName("UTF-8"));
  46. //读文件内容
  47. while (reader.readRecord()) {
  48. datas.add(reader.getValues());
  49. }
  50. return datas;
  51. }
  52. }

代码解释

首先,Mahout版本为0.8,目前最权威的参考书《Mahout in action》里是基于0.5的版本的介绍,这会有一些区别,比如,0.8中已经没有Cluster类了,替代为了Kluster;同时,0.8中没有了书中描述得基于内存(in-memory)形式的聚类算法。再则,如果你本机并没有hadoop的环境,那么在执行KMeansDriver.run或FuzzyKMeansDriver.run的时候,最后一个参数必须置为true,否则会执行不通过。其他的大致与书中介绍的一致。
聚类的步骤总结如下:
预处理数据-->生成Vector格式-->写入SenquenceFile--> 设定中心点-->执行聚类程序-->结果分析

最后

最后得到的典型结果如下:

  1. 0有:合肥工业大学、重庆大学、河海大学、中国矿业大学、同济大学、华东理工大学、西安交通大学、大连理工大学、天津大学、中国海洋大学
  2. 1有:北京科技大学、西安电子科技大学、北京化工大学、中国地质大学(北京)、北京邮电大学、中国石油大学(北京)、中国矿业大学(北京)、中国石油大学(华东)、华北电力大学
  3. 2有:上海交通大学、中南大学、武汉理工大学、兰州大学、东南大学、四川大学、湖南大学、江南大学、华中科技大学、浙江大学、华南理工大学
  4. 3有:中央戏剧学院、中央美术学院、中央音乐学院、国际关系学院
  5. 4有:南京农业大学、北京林业大学、西北农林科技大学、华中农业大学、东北林业大学、中国农业大学
  6. 5有:上海财经大学、中央财经大学、西南财经大学、对外经济贸易大学、陕西师范大学、中南财经政法大学、中国政法大学
  7. 6有:北京外国语大学、北京语言大学、中国药科大学、北京中医药大学、中国传媒大学、上海外国语大学
  8. 7有:东华大学、长安大学、东北大学、北京交通大学、西南交通大学、中国地质大学(武汉)、电子科技大学、清华大学
  9. 8有:武汉大学、厦门大学、中国人民大学、南京大学、南开大学、山东大学、北京大学、复旦大学、吉林大学、中山大学、西南大学
  10. 9有:华东师范大学、华中师范大学、东北师范大学、北京师范大学

从结果来看,这个聚类准确度还是非常高的,由此我们也可以知道,在社科方面,我校与很多知名偏理工类学校都很一般,即便是清华也只是跟电子科大等大学比肩,而武大却是跟北大统一梯队,站稳了国内社科类的第一把交椅。
在写这个例子的时候,还是碰到了很多困难的,因为我主要就是在参考《Mahout in action》,而0.5到0.8的演变中还是有了相当的变化,所以经常会碰到一些异常,这个时候就必须得发挥查找资料的能力了,在完成这个例子的过程中,我主要得到了以下几个帮助,希望可以让其他人避免走弯路。
1、http://qnalist.com/questions/4878396/in-memory-kmeans-clustering
2、http://stackoverflow.com/questions/9565998/getting-an-ioexception-when-running-a-sample-code-in-mahout-in-action-on-mahou
3、http://ghost-face.iteye.com/blog/1905255
4、http://www.tuicool.com/articles/ryiEN3

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注