因为涉及到机器学习,首先一点需要说明的就是为什么使用Mapreduce而不是Spark,其实Mapreduce之所以一直被人诟病就是因为Mapreduce是基于磁盘交互的迭代计算框架,在迭代过程中(不是中间shuffle结果)都是基于磁盘交互,也就是写入磁盘再从磁盘中读出。这里需要说明的是很多文章对于Spark和Mapreduce的定义就是一个是基于内存交互,一个是基于磁盘交互,当然这样说更省力更好理解,但是往往都是对此的误解,因为不管是哪一个框架,中间shuffle结果都是基于磁盘的,也是一个小坑。
最终影响速度的就是迭代结果的io消耗。
当然,就算如此我们也可以用Spark,不错当然可以,前提你是有一台性能较好的服务器或者自用电脑,本人在用16GB内存的电脑,搭建分布式集群在执行复杂度较高的算法,并且有一定量的数据时,报错这是最难解决的,当然排查日志当然是第一首选手段,你要知道几十MB的log日志打开电脑基本上是卡死状态,你也可以用某些比较好的中间软件notepad++,不过一样使用手感极差。所以我觉得在某种程度上MR框架是优于Spark的。
回归整题
那么Mapreduce如何可以实现机器学习算法呢?我相信不少人使用过Mahout框架,上面确实集成了不少的经典算法,但是在排查源码时,和我们自己手动写的Map和Reduce框架还是有所不同,当然你可以深追源码。
首先实现机器学习,比如聚类算法的Kmeans首先需要解决的不是参数问题,而是变量问题,在分块之后如何可以将簇质心位置同步给每一个节点。
tips:我发现大家对分布式有一个误解,那就是分布式是分布式进行计算结果,而不是分布式聚类,得到若干个聚类结果,然后进行聚类集成。首先聚类集成是一个非常难的问题,尤其是在标签问题上,很多文章提出用投票法来解决标签问题来合并簇,我觉得并非是一个好的选择。
因为要分发,所以引入一个概念:全局变量
查阅比较多的资料并且在实际中我主要是用到了两种全局变量。
- 在driver类中的conf设置参数
- 使用distributioncache来分发文件
首先来说明第一种,我觉得完全没用,首先如果你在编码阶段知道的实际参数值,你完全可以在map和reduce阶段进行赋值,如果是需要在外部接收参数,在使用hadoop jar方式进行运行时是完全接收不到,当然这里使用的是将参数分解提取,无效
第二种就是机器学习实现的关键,可以将参数存入文件中,在读取,尤其是在机器学习中这种全局变量的方式是最好的。
job.addCacheFile(new Path("1.txt").toUri());
在driver中添加如下代码,便可对所有节点进行分发文件。
另外需要补充的就是,在Reduce中使用全局变量(类全局变量)无效,具体是什么原因还需要排查源码才可知道,但是在Map(类全局变量)有效。
在Reduce中定义集合,添加对象会使得集合中重复最后一条元素。
好了至此,Mapreduce实现机器学习的步骤已经讲完,Kmeans并不是多么难的算法可以自己动手来写一写,下面贴一部分代码。
public static Path buildClusters(Configuration conf, Path input, Path clustersIn, Path output,
int maxIterations, String delta, boolean runSequential) throws IOException,
InterruptedException, ClassNotFoundException {
double convergenceDelta = Double.parseDouble(delta);
List<Cluster> clusters = new ArrayList<>();
KMeansUtil.configureWithClusterInfo(conf, clustersIn, clusters);
if (clusters.isEmpty()) {
throw new IllegalStateException("No input clusters found in " + clustersIn + ". Check your -c argument.");
}
Path priorClustersPath = new Path(output, Cluster.INITIAL_CLUSTERS_DIR);
ClusteringPolicy policy = new KMeansClusteringPolicy(convergenceDelta);
ClusterClassifier prior = new ClusterClassifier(clusters, policy);
prior.writeToSeqFiles(priorClustersPath);
if (runSequential) {
ClusterIterator.iterateSeq(conf, input, priorClustersPath, output, maxIterations);
} else {
ClusterIterator.iterateMR(conf, input, priorClustersPath, output, maxIterations);
}
return output;
}
特地说明一下,对于Mapreduce来实现Kmeans,串行job,然后根据簇质心或者其他收敛条件皆可。
最后祝大家五一快乐!!