Mahout聚類演算法學習之Canopy演算法的分析與實現

Thinkgamer_gyt發表於2015-10-09
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的根目錄下,執行下面的命令:
 

  1. $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聚類中心向量程式碼
 

  1. package mahout.fansy.utils;  
  2. import java.io.IOException;  
  3. import org.apache.hadoop.conf.Configuration;  
  4. import org.apache.hadoop.io.Text;  
  5. import org.apache.hadoop.mapreduce.Job;  
  6. import org.apache.hadoop.mapreduce.Mapper;  
  7. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  8. import org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat;  
  9. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  10. import org.apache.hadoop.util.ToolRunner;  
  11. import org.apache.mahout.clustering.iterator.ClusterWritable;  
  12. import org.apache.mahout.common.AbstractJob;  
  13. import org.slf4j.Logger;  
  14. import org.slf4j.LoggerFactory;  
  15. /**  
  16.  ??* read cluster centers  
  17.  ??* @author fansy  
  18.  ??*/  
  19. public class ReadClusterWritable extends AbstractJob {  
  20.       public static void main(String[] args) throws Exception{  
  21.            ToolRunner.run(new Configuration(), new ReadClusterWritable(),args);  
  22.       }  
  23.       @Override  
  24.       public int run(String[] args) throws Exception {  
  25.            addInputOption();  
  26.            addOutputOption();  
  27.            if (parseArguments(args) == null) {  
  28.                 return -1;  
  29.              ?}  
  30.            Job job=new Job(getConf(),getInputPath().toString());  
  31.            job.setInputFormatClass(SequenceFileInputFormat.class);  
  32.            job.setMapperClass(RM.class);  
  33.            job.setMapOutputKeyClass(Text.class);  
  34.            job.setMapOutputValueClass(Text.class);  
  35.            job.setNumReduceTasks(0);  
  36.            job.setJarByClass(ReadClusterWritable.class);  
  37.  
  38.            FileInputFormat.addInputPath(job, getInputPath());  
  39.            FileOutputFormat.setOutputPath(job, getOutputPath());  
  40.            ???if (!job.waitForCompletion(true)) {  
  41.               throw new InterruptedException("Canopy Job failed processing " + getInputPath());  
  42.         }  
  43.       ?return 0;  
  44.     }  
  45.     public static class RM extends Mapper<Text,ClusterWritable ,Text,Text>{  
  46.          private Logger log=LoggerFactory.getLogger(RM.class);  
  47.        ???public void map(Text key,ClusterWritable value,Context context) throws  
  48. IOException,InterruptedException{  
  49.               String str=value.getValue().getCenter().asFormatString();  
  50.          //   System.out.println("center****************:"+str);  
  51.            ?log.info("center*****************************:"+str); // set log information  
  52.               context.write(key, new Text(str));  
  53.          }  
  54.     }  

把上面的程式碼編譯打包放入/home/mahout/mahout_jar目錄下,執行下面的命令:

  1. $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 

相關文章