Mahout聚类算法学习之Canopy算法的分析与实现

3.1 Canopy算法
3.1.1 Canopy算法简介
     Canopy算法的主要思想是把聚类分为两个阶段:阶段一,通过使用一个简单、快捷的距离计算方法把数据分为可重叠的子集,称为“canopy”;阶段二,通过使用一个精准、严密的距离计算方法来计算出现在阶段一中同一个canopy的所有数据向量的距离。这种方式和之前的聚类方式不同的地方在于使用了两种距离计算方式,同时因为只计算了重叠部分的数据向量,所以达到了减少计算量的目的。
    具体来说,阶段一,使用一个简单距离计算方法来产生具有一定数量的可重叠的子集。canopy就是一个样本数据集的子集,子集中的样本数据是通过一个粗糙的距离计算方法来计算样本数据向量和canopy的中心向量的距离,设定一个距离阈值,当计算的距离小于这个阈值的时候,就把样本数据向量归为此canopy。这里要说明的是,每个样本数据向量有可能存在于多个canopy里面,但是每个样本数据向量至少要包含于一个canopy中。canopy的创建基于不存在于同一个canopy中的样本数据向量彼此很不相似,不能被分为同一个类的这样的观点考虑的。由于距离计算方式是粗糙的,因此不能够保证性能(计算精确度)。但是通过允许存在可叠加的canopy和设定一个较大的距离阈值,在某些情况下可以保证该算法的性能。
图3-1是一个canopy的例子,其中包含5个数据中心向量。

     图3-1中数据向量用同样灰度值表示的属于同一个聚类。聚类中心向量A被随机选出,然后以A数据向量创建一个canopy,这个canopy包括所有在其外圈(实线圈)的数据向量,而内圈(虚线)中的数据向量则不再作为中心向量的候选名单。
那么针对一个具体的canopy应该如何创建呢?下面介绍创建一个普通的canopy算法的步骤。
1)原始数据集合List按照一定的规则进行排序(这个规则是任意的,但是一旦确定就不再更改),初始距离阈值为T1、T2,且T1 > T2(T1、T2的设定可以根据用户的需要,或者使用交叉验证获得)。
2)在List中随机挑选一个数据向量A,使用一个粗糙距离计算方式计算A与List中其他样本数据向量之间的距离d。
3)根据第2步中的距离d,把d小于T1的样本数据向量划到一个canopy中,同时把d小于T2的样本数据向量从候选中心向量名单(这里可以理解为就是List)中移除。
4)重复第2、3步,直到候选中心向量名单为空,即List为空,算法结束。
图3-2为创建canopy算法的流程图。

    阶段二,可以在阶段一的基础上应用传统聚类算法,比如贪婪凝聚聚类算法、K均值聚类算法,当然,这些算法使用的距离计算方式是精准的距离计算方式。但是因为只计算了同一个canopy中的数据向量之间的距离,而没有计算不在同一个canopy的数据向量之间的距离,所以假设它们之间的距离为无穷大。例如,若所有的数据都简单归入同一个canopy,那么阶段二的聚类就会退化成传统的具有高计算量的聚类算法了。但是,如果canopy不是那么大,且它们之间的重叠不是很多,那么代价很大的距离计算就会减少,同时用于分类的大量计算也可以省去。进一步来说,如果把Canopy算法加入到传统的聚类算法中,那么算法既可以保证性能,即精确度,又可以增加计算效率,即减少计算时间。
Canopy算法的优势在于可以通过第一阶段的粗糙距离计算方法把数据划入不同的可重叠的子集中,然后只计算在同一个重叠子集中的样本数据向量来减少对于需要距离计算的样本数量。
3.1.2 Mahout中Canopy算法实现原理
在Mahout中,Canopy算法用于文本的分类。实现Canopy算法包含三个MR,即三个Job,可以描述为下面4个步骤。
1)Job1:将输入数据处理为Canopy算法可以使用的输入格式。
2)Job2:每个mapper针对自己的输入执行Canopy聚类,输出每个canopy的中心向量。
3)Job2:每个reducer接收mapper的中心向量,并加以整合以计算最后的canopy的中心向量。
4)Job3:根据Job2的中心向量来对原始数据进行分类。
其中,Job1和Job3属于基础操作,这里不再进行详细分析,而主要对Job2的数据流程加以简要分析,即只对Canopy算法的原理进行分析。
首先来看图3-3,可以根据这个图来理解Job2的map/reduce过程。

图3-3中的输入数据可以产生两个mapper和一个reducer。每个mapper处理其相应的数据,在这里处理的意思是使用Canopy算法来对所有的数据进行遍历,得到canopy。具体如下:首先随机取出一个样本向量作为一个canopy的中心向量,然后遍历样本数据向量集,若样本数据向量和随机样本向量的距离小于T1,则把该样本数据向量归入此canopy中,若距离小于T2,则把该样本数据从原始样本数据向量集中去除,直到整个样本数据向量集为空为止,输出所有的canopy的中心向量。reducer调用Reduce过程处理Map过程的输出,即整合所有Map过程产生的canopy的中心向量,生成新的canopy的中心向量,即最终的结果。

 

3.1.3 Mahout的Canopy算法实战
1.输入数据
http://archive.ics.uci.edu/m1/databases/synthetic_control/synthetic_control.data.html下载数据,这里使用的数据同样是第2章中提到的控制图数据,包含600个样本数据,每个样本数据有60个属性列,这些数据可以分为六类。我们首先上传该文本数据到HDFS,使用如下命令:

 

$HADOOP_HOME/bin/hadoop fs –copyFromLocal /home/mahout/mahout_data/synthetic_control.data input/synthetic_control.data 

这里只针对Job2的任务进行实战:Job2的输入要求使用的数据是序列化的,同时要求输入数据要按照一定的格式,因此,编写代码清单3-1对原始数据进行处理。

代码清单 3-1 原始数据处理代码

package mahout.fansy.utils.transform;  
import java.io.IOException;  
import org.apache.hadoop.conf.Configuration;  
import org.apache.hadoop.fs.Path;  
import org.apache.hadoop.io.LongWritable;  
import org.apache.hadoop.io.Text;  
import org.apache.hadoop.mapreduce.Job;  
import org.apache.hadoop.mapreduce.Mapper;  
import org.apache.hadoop.mapreduce.Reducer;  
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;  
import org.apache.hadoop.util.ToolRunner;  
import org.apache.mahout.common.AbstractJob;  
import org.apache.mahout.math.RandomAccessSparseVector;  
import org.apache.mahout.math.Vector;  
import org.apache.mahout.math.VectorWritable;  
/**  
 ??* transform text data to vectorWritable data  
 ??* @author fansy  
 ??*  
 ??*/  
public class Text2VectorWritable extends AbstractJob{  
     public static void main(String[] args) throws Exception{  
          ToolRunner.run(new Configuration(), new Text2VectorWritable(),args);  
     }  
     @Override  
     public int run(String[] arg0) throws Exception {  
          addInputOption();  
          addOutputOption();  
          if (parseArguments(arg0) == null) {  
               return -1;  
          }  
          Path input=getInputPath();  
          Path output=getOutputPath();  
          Configuration conf=getConf();  
          // set job information  
        ?Job job=new Job(conf,"text2vectorWritableCopy with input:"+input.getName());  
          job.setOutputFormatClass(SequenceFileOutputFormat.class);  
          job.setMapperClass(Text2VectorWritableMapper.class);  
          job.setMapOutputKeyClass(LongWritable.class);  
          job.setMapOutputValueClass(VectorWritable.class);  
          job.setReducerClass(Text2VectorWritableReducer.class);  
          job.setOutputKeyClass(LongWritable.class);  
          job.setOutputValueClass(VectorWritable.class);  
          job.setJarByClass(Text2VectorWritable.class);  
          FileInputFormat.addInputPath(job, input);  
          SequenceFileOutputFormat.setOutputPath(job, output);  
          if (!job.waitForCompletion(true)) { // wait for the job is done  
              throw new InterruptedException("Canopy Job failed processing " + input);  
              }  
          return 0;  
    }  
    /**  
     ??* Mapper :main procedure  
     ??* @author fansy  
     ??*  
     ??*/  
    public static class Text2VectorWritableMapper extends Mapper<LongWritable,Text,LongWritable,VectorWritable>{  
         public void map(LongWritable key,Text value,Context context)throws  
IOException,InterruptedException{  
             ??String[] str=value.toString().split("\\s{1,}");  
             // split data use one or more blanker  
             ???Vector vector=new RandomAccessSparseVector(str.length);  
             ???for(int i=0;i<str.length;i++){  
             ???     vector.set(i, Double.parseDouble(str[i]));  
             ???}  
             ???VectorWritable va=new VectorWritable(vector);  
             ???context.write(key, va);  
         }  
    }  
    /**  
     ??* Reducer: do nothing but output  
     ??* @author fansy  
     ??*  
     ??*/  
    public static class Text2VectorWritableReducer extends Reducer<LongWritable,  
VectorWritable,LongWritable,VectorWritable>{  
         public void reduce(LongWritable key,Iterable<VectorWritable> values,Con-text context)throws IOException,InterruptedException{  
             ???for(VectorWritable v:values){  
             ?     context.write(key, v);  
             ???}  
         }  
    }  
} 

 

把上面的代码编译打包成ClusteringUtils.jar并放入/home/mahout/mahout_jar目录下,然后在Hadoop根目录下运行下面的命令:

 

$HADOOP_HOME/bin/hadoop jar /home/mahout/mathout_jar/ClusteringUtils.jar  
mahou·t.fansy.utils.transform.Text2VectorWritable –i input/synthetic_control.data –o  
input/transform 

命令运行成功后可以在文件监控系统查看转换后的输入数据,如图3-5所示。

由图3-5方框中的内容可以看出,数据已经被转换为VectorWritable的序列文件了。经过上面的步骤,输入数据的准备工作就完成了。

提示 在Hadoop中运行编译打包好的jar程序,可能会报下面的错误:

 

Exception in thread "main" java.lang.NoClassDefFoundError:  
org/apache/mahout/common/AbstractJob 

这时需要把Mahout根目录下的相应的jar包复制到Hadoop根目录下的lib文件夹下,同时重启Hadoop即可。

 

2.运行

进入Mahout的根目录下,运行下面的命令:

$MAHOUT_HOME/bin/mahout canopy --input input/transform/part-r-00000 --output output/canopy --distanceMeasure org.apache.mahout.common.distance.EuclideanDistanceMeasure --t1 80 --t2 55 --t3 80 --t4 55 --clustering 


其中输入文件使用的是转换后的序列文件;距离计算方式使用的是欧式距离;T1和T3设置为80,T2和T4设置为55;--clustering选项表示最后对原始数据进行分类。

可以看到其输出类名为ClusterWritable,编写下面的代码清单 3-2。

代码清单3-2 转换canopy聚类中心向量代码

package mahout.fansy.utils;  
import java.io.IOException;  
import org.apache.hadoop.conf.Configuration;  
import org.apache.hadoop.io.Text;  
import org.apache.hadoop.mapreduce.Job;  
import org.apache.hadoop.mapreduce.Mapper;  
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
import org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat;  
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
import org.apache.hadoop.util.ToolRunner;  
import org.apache.mahout.clustering.iterator.ClusterWritable;  
import org.apache.mahout.common.AbstractJob;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
/**  
 ??* read cluster centers  
 ??* @author fansy  
 ??*/  
public class ReadClusterWritable extends AbstractJob {  
      public static void main(String[] args) throws Exception{  
           ToolRunner.run(new Configuration(), new ReadClusterWritable(),args);  
      }  
      @Override  
      public int run(String[] args) throws Exception {  
           addInputOption();  
           addOutputOption();  
           if (parseArguments(args) == null) {  
                return -1;  
             ?}  
           Job job=new Job(getConf(),getInputPath().toString());  
           job.setInputFormatClass(SequenceFileInputFormat.class);  
           job.setMapperClass(RM.class);  
           job.setMapOutputKeyClass(Text.class);  
           job.setMapOutputValueClass(Text.class);  
           job.setNumReduceTasks(0);  
           job.setJarByClass(ReadClusterWritable.class);  
 
           FileInputFormat.addInputPath(job, getInputPath());  
           FileOutputFormat.setOutputPath(job, getOutputPath());  
           ???if (!job.waitForCompletion(true)) {  
              throw new InterruptedException("Canopy Job failed processing " + getInputPath());  
        }  
      ?return 0;  
    }  
    public static class RM extends Mapper<Text,ClusterWritable ,Text,Text>{  
         private Logger log=LoggerFactory.getLogger(RM.class);  
       ???public void map(Text key,ClusterWritable value,Context context) throws  
IOException,InterruptedException{  
              String str=value.getValue().getCenter().asFormatString();  
         //   System.out.println("center****************:"+str);  
           ?log.info("center*****************************:"+str); // set log information  
              context.write(key, new Text(str));  
         }  
    }  
} 

把上面的代码编译打包放入/home/mahout/mahout_jar目录下,运行下面的命令:

$HADOOP_HOME/bin/hadoop jar /home/mahout/mahout_jar/ClusteringUtils.jar mahout.fansy.utils.ReadClusterWritable -i output/canopy/clusters-0-final/part-r-00000 -o output/canopy-output 

 


搜索与推荐Wiki

扫一扫 关注微信公众号!号主 专注于搜索和推荐系统,尝试使用算法去更好的服务于用户,包括但不局限于机器学习,深度学习,强化学习,自然语言理解,知识图谱,还不定时分享技术,资料,思考等文章!


                             【技术服务】,详情点击查看:https://mp.weixin.qq.com/s/PtX9ukKRBmazAWARprGIAg


外包服务

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java中实现聚类算法需要用到一些机器学习相关的库,比如Weka、Apache Mahout等。下面以Weka为例,简单介绍如何使用Java实现聚类算法。 1. 导入Weka库 在Java工程中导入Weka库,可以使用Maven或手动导入方式。 Maven依赖: ``` <dependency> <groupId>nz.ac.waikato.cms.weka</groupId> <artifactId>weka-dev</artifactId> <version>3.9.5</version> </dependency> ``` 手动导入: 下载Weka的jar包,然后在项目中添加该jar包。 2. 加载数据 聚类算法需要先加载数据,可以从文件中读取数据,也可以直接定义数据集。 从文件读取数据示例: ``` BufferedReader reader = new BufferedReader(new FileReader("data.arff")); Instances data = new Instances(reader); reader.close(); ``` 直接定义数据集示例: ``` FastVector attributes = new FastVector(); attributes.addElement(new Attribute("attribute1")); attributes.addElement(new Attribute("attribute2")); ... Instances data = new Instances("data", attributes, 0); ``` 3. 构建聚类模型 Weka提供了多种聚类算法,如KMeans、EM等。在构建聚类模型时需要指定聚类算法和对应的参数。 KMeans聚类算法示例: ``` SimpleKMeans kMeans = new SimpleKMeans(); kMeans.setNumClusters(3); kMeans.buildClusterer(data); ``` 4. 应用聚类模型 构建好聚类模型后,可以使用该模型对数据进行聚类,得到每个数据点所属的聚类。 ``` for (int i = 0; i < data.numInstances(); i++) { int cluster = kMeans.clusterInstance(data.instance(i)); System.out.println("Instance " + i + " is assigned to cluster " + cluster); } ``` 以上是使用Java实现聚类算法的基本流程,实际应用中还需要进行数据预处理、模型评估等步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值