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
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();
}
}
最后聚类结果如下:
{"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!