mahout最新版+hadoop2.4.1运行kmeans分布式算法

1、理论须知

用过mahout和hadoop集成的朋友们,都经过很多折腾,mahout这个东西是包括了好多的机器学习算法,确实我们调用起来相当方便,毕竟我们不需要为了使用一个算法重新编码。但是mahout0.10之前都只能支持到hadoop1.x版本,所以大部分使用hadoop2.x的朋友,很苦恼,虽然网上各种办法,大都折腾的很,浪费时间且错误百出,鉴于此,将本人集成的成功案例分享给大家,少走弯路,用好点赞!

废话不多说,先知道kmeans算法的基本步骤,以知其所以然:

在Hadoop分布式环境下实现K-Means聚类算法的伪代码如下:

输入:参数0--存储样本数据的文本文件inputfile;

            参数1--存储样本数据的SequenceFile文件inputPath;

            参数2--存储质心数据的SequenceFile文件centerPath;

            参数3--存储聚类结果文件(SequenceFile文件)所处的路径clusterPath;

            参数4--类的数量k;

输出:k个类

Begin

           读取inputPath,从中选取前k个点作为初始质心,将质心数据写入centerPath;

           While 聚类终止条件不满足

                    在Mapper阶段,读取inputPath,对于key所对应的点,遍历所有的质心,选择最近的质心,将该质心的编号作为键,

                    该点的编号作为值传递给Reducer;

                    在Reducer阶段,将Mapper阶段传递过来的值根据键归并输出,结果写入clusterPath;

                    读取clusterPath,重新计算质心,将结果写入centerPath;

           EndWhile

End

判断聚类效果好坏的常见指标是下述的准则函数值:

2.Eclipse中实现mahout分布式聚类算法

 第1步:关键一步啊。

解决方法就是下载最新的源码,并且编译成Hadoop 2.x兼容模式,下面是具体编译方法:

1. 使用git命令克隆Mahout最新的源码到本地,目前最新的版本是1.0-SNAPSHOT。

git clone https://github.com/apache/mahout.git


2. Mahout源代码下载完成后,直接使用mvn命令编译源代码,注意要加上hadoop2.version=2.4.1参数让编译后的Mahout可以兼容Hadoop 2.4.1版本。这里版本可以填写任何的2.x版本。

mvn -Dhadoop2.version=2.4.1 -DskipTests clean install

这一步在cmd控制台运行即可,可能需要较长时间,打好的包会进入maven本地仓库。
 

 第2步:新建一个maven项目,这个不多说。pom.xml中需要引入最新的mahout版本

  

  
        	org.apache.mahout
        	mahout-math
        	0.13.2-SNAPSHOT
        
        
        	org.apache.mahout
        	mahout-mr
        	0.13.2-SNAPSHOT
        
        
        	org.apache.mahout
        	mahout-hdfs
        	0.13.2-SNAPSHOT
        
        
        	org.apache.mahout
        	mahout-h2o_2.10
        	0.13.2-SNAPSHOT
        
        
        	commons-httpclient
        	commons-httpclient
        	3.0
         
        
这步之后,所有jar包已经导入项目中。ok准备工作完成。

第3步:拷贝hadoop服务器的core-site.xml,hdfs-site.xml及mapred-site.xml文件到项目的hadoop目录下。大致内容如下:

core-site.xml:





   
            fs.defaultFS
            hdfs://192.168.10.127:9000
        
        
        
            hadoop.tmp.dir
            /home/hadoop/hadoop/tmp
           
hdfs-site.xml:





   
  
            dfs.replication
            1
    
  
    dfs.permissions
    false
  
注意红色部分,这样设置访问hdfs文件不会提示没有权限。

mapred-site.xml:





  
    mapred.job.tracker
    hdfs://192.168.10.127:9001
  


第4步:编写调用kmeans算法java代码,具体如下(一些重要地方做了注释),

package org.conan.mymahout.cluster08;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.Text;
import org.apache.mahout.clustering.Cluster;
import org.apache.mahout.clustering.iterator.ClusterWritable;
import org.apache.mahout.clustering.kmeans.KMeansDriver;
import org.apache.mahout.clustering.kmeans.Kluster;
import org.apache.mahout.common.distance.EuclideanDistanceMeasure;
import org.apache.mahout.math.RandomAccessSparseVector;
import org.apache.mahout.math.Vector;
import org.apache.mahout.math.VectorWritable;
public class KMeansHadoop13 {
	 private static final String HDFS = "hdfs://192.168.10.127:9000";
	public static final double[][] points = {
		{1, 1}, {2, 1}, {1, 2},
		{2, 2}, {3, 3}, {8, 8},
		{9, 8}, {8, 9}, {9, 9}};

		public static void writePointsToFile(List points,String fileName,FileSystem fs,	Configuration conf) throws IOException {
			Path path = new Path(fileName);
			SequenceFile.Writer writer = new SequenceFile.Writer(fs, conf,path, LongWritable.class, VectorWritable.class);
			long recNum = 0;
			VectorWritable vec = new VectorWritable();
			for (Vector point : points) {
			vec.set(point);
			writer.append(new LongWritable(recNum++), vec);
			}
			writer.close();
		}

		public static List getPoints(double[][] raw) {
			List points = new ArrayList();
			for (int i = 0; i < raw.length; i++) {
			double[] fr = raw[i];
			Vector vec = new RandomAccessSparseVector(fr.length);
			vec.assign(fr);
			points.add(vec);
			}
			return points;
		}

		public static void main(String args[]) throws Exception {
			System.setProperty("HADOOP_USER_NAME", "hadoop");
		int k = 2;

		List vectors = getPoints(points);//根据初始点坐标数组构建成为聚类算法能够处理的vector的list集合格式

		File testData = new File("clustering/testdata");//在hdfs创建存放数据集的目录
		if (!testData.exists()) {
		testData.mkdir();
		}
		testData = new File("clustering/testdata/points");
		if (!testData.exists()) {
		testData.mkdir();
		}

		Configuration conf = new Configuration();
		 conf.addResource("classpath:/hadoop/core-site.xml");
	        conf.addResource("classpath:/hadoop/hdfs-site.xml");
	        conf.addResource("classpath:/hadoop/mapred-site.xml");
	        FileSystem fs = FileSystem.get(new URI("hdfs://192.168.10.127:9000/"), conf, "hadoop");
		writePointsToFile(vectors, "clustering/testdata/points/file1", fs, conf);//将点的集合写到hdfs上的数据文件中名称为file1

		Path path = new Path("clustering/testdata/clusters/part-00000");//序列化文件的路径
		SequenceFile.Writer writer = new SequenceFile.Writer(fs, conf, path, Text.class, Kluster.class);
		
		// 初始化中心点k=2
		 
		for (int i = 0; i < k; i++) {
		Vector vec = vectors.get(i);
		//cluster--{"r":[],"c":[1.0,1.0],"n":0,"identifier":"CL-0"}
		Kluster cluster = new Kluster(vec, i, new EuclideanDistanceMeasure());
		writer.append(new Text(cluster.getIdentifier()), cluster);
		}
		writer.close();
		//运行聚类算法
		KMeansDriver.run(conf,
		new Path(HDFS+"/user/hadoop/clustering/testdata/points"),//原始输入
		new Path(HDFS+"/user/hadoop/clustering/testdata/clusters/part-00000"),//初始中心点集合
		new Path(HDFS+"/user/hadoop/clustering/output"),//聚类结果
		0.001,
		3,
		true,
		0,
		false);
		//读取聚类结果
		//@SuppressWarnings("deprecation")
		SequenceFile.Reader reader = new SequenceFile.Reader(fs,
		new Path("clustering/output/" +Cluster.CLUSTERS_DIR+"3"+ Cluster.FINAL_ITERATION_SUFFIX + "/part-r-00000"), conf);
		IntWritable key = new IntWritable();
//		WeightedPropertyVectorWritable value = new WeightedPropertyVectorWritable();针对本地文件
		ClusterWritable value=new ClusterWritable();//针对集群,也就是hadoop中的文件
		while (reader.next(key, value)) {
		System.out.println(value.getValue().toString() + " belongs to cluster " + key.toString());
		}
		reader.close();
		
		 
	      
		}

}

说明:该聚类算法迭代了3次,算法采用的数据是九个二维平面的点,数组存储了他们的坐标。运行速度非常快,如果采用样本文件数据,迭代次数太多会导致聚类速度太慢,十几分钟都很正常。最后用ClusterWritable来展示结果,新版本mahout0.13,所以没有之前版本的 WeightedPropertyVectorWritable 类来展示结果。

最后聚类结果如下:

{"r":[0.748,0.748],"c":[1.8,1.8],"n":5,"identifier":"VL-0"} belongs to cluster 0
{"r":[0.5,0.5],"c":[8.5,8.5],"n":4,"identifier":"VL-1"} belongs to cluster 1


4、补充
如果以上程序运行过程中,报org.apache.hadoop.io.nativeio.NativeIO$Windows.access的错误,千万不要用网上的一些做法,搞个什么hadoop.dll拷贝到c盘系统目录等等,太折腾,而且如果dll版本不对应,操作系统位数不对都会报错,给大家一个直接解决问题的办法,你可以看到错误报到了NativeIO的570行,window.access返回false,所以没有权限,大家可以直接修改为return true,即可windows和hadoop的linux机器传输数据。办法就是到hadoop2.4.1解压缩包中找到NativeIO源码,拷贝到你自己项目中,当然要遵循原来的包结构,这样就可以正常运行。

ok,收工,希望各位从中获益,一切顺利且happy!



你可能感兴趣的:(MachineLearning,mahout,hadoop,聚类算法,kmeans)