mahout源码分析之Decision Forecast 三部曲之一Describe
Mahout版本:0.7,hadoop版本:1.0.4,jdk:1.7.0_25 64bit。
Mahout中实现决策树算法的有两个(quick start),分别是 Partial Implementation和 Breiman Example,可以点击链接到相应的网页查看其官方实例。其中Breiman Example是单机版的,而Partial Implementation是可以使用map-reduce模式的。
Partial Implementation可以分为三步:Describe、BuildForecast、TestForecast,共称为决策树三部曲。以前有写过相关的内容,今次重新写这个算法的分析,应该会有一些更加深入的认知。本篇介绍三部曲之一Describe。
Describe在mahout-examples-0.7-job.jar包中的\org\apache\mahout\classifier\df\tools 路径下,在myeclipse中打开此文件,可以看到该类的源码。直接运行该类(含有main函数,可以直接运行),可以看到该类的使用指南:
Usage: [--path <path> --file <file> --descriptor <descriptor1> [<descriptor2> ...] --regression --help] Options --path (-p) path Data path --file (-f) file Path to generated descriptor file --descriptor (-d) descriptor [descriptor ...] data descriptor --regression (-r) Regression Problem --help (-h)
该类主要的作用是把原始文件的描述写入一个文件。进入main函数,可以看到代码刚开始都是参数的传递,使用Option类来进行参数解析。然后就直接到了runTool()方法,这个是主要的操作,看到这个函数的参数有:dataPath(原始数据的输入路径),descriptor(对原始文件的描述,list),descPath(描述文件生成的路径),regression(是否是回归问题,由于这里做的是非回归问题,所以这个参数可以暂时忽略)。
采用的测试数据: glass.data,测试类:
package test.breiman; import java.io.IOException; import java.util.Arrays; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.mahout.classifier.df.data.DescriptorException; import org.apache.mahout.classifier.df.tools.Describe; import org.apache.mahout.common.HadoopUtil; public class DescribeFollow { /** * @param 测试Describe * @throws DescriptorException * @throws IOException */ public static void main(String[] args) throws IOException, DescriptorException { String[] arg=new String[]{"-p","hdfs://ubuntu:9000/user/breiman/input/glass.data", "-f","hdfs://ubuntu:9000/user/breiman/glass.info2","-d","I","9","N","L"}; // System.out.println(arg[Arrays.asList(arg).indexOf("-f")+1]); HadoopUtil.delete(new Configuration(), new Path(arg[Arrays.asList(arg).indexOf("-f")+1])); Describe.main(arg); } }首先简单介绍下输入数据:
1,1.52101,13.64,4.49,1.10,71.78,0.06,8.75,0.00,0.00,1 2,1.51761,13.89,3.60,1.36,72.73,0.48,7.83,0.00,0.00,1
每一个样本都有11个维度,第一维度是样本的编号(从1开始),最后一维是样本的标签。中间9个维度是样本的属性,都是数值型的。所以--descriptor参数设置为[I,9,N,L],I表示为忽视,是ignore的缩写,N是Numerical的缩写,L表示Label。当然如果维度中有非数值型的属性,也是可以的用C表示(Categorical的缩写)。9表示九个都是N,如果属性是这样的[Ignore,Numerical,Numerical,Categorical,Numerical,Categorical,Categorical,Label],那么--descriptor参数就应该写为下面的方式:[I,2,N,C,N,2,C,L]。71,1.51574,14.86,3.67,1.74,71.87,0.16,7.36,0.00,0.12,2 72,1.51848,13.64,3.87,1.27,71.96,0.54,8.32,0.00,0.32,2
在runTool()里面的第一行设置断点,可以看到形参中的descriptor是:[I, 9, N, L]。在runTool中一共进行了四个操作:
String descriptor = DescriptorUtils.generateDescriptor(description); Path fPath = validateOutput(filePath); Dataset dataset = generateDataset(descriptor, dataPath, regression); DFUtils.storeWritable(new Configuration(), fPath, dataset);其中的validateOutput应该可以忽略的(主要是判断输出文件是否存在而已,在DescribeFollow的时候不管输出文件是否存在都把它删除了,所以这里肯定是不存在问题的了)。那么generateDescriptor方法是做什么用的呢?就是一个转换,debug直接进行下一步,可以看到descriptor的值为:[I N N N N N N N N N L],就等于是把数字表示的全部转为字符了。generateDataset方法对应的是DataLoader.generateDataset(descriptor, regression, fs, path)这个方法,进入DataLoader类里面的这个方法。这个方法内主要进行了三个操作:
Attribute[] attrs = DescriptorUtils.parseDescriptor(descriptor); if (parseString(attrs, valsets, line, regression)) { size++; } List<String>[] values = new List[attrs.length]; for (int i = 0; i < valsets.length; i++) { if (valsets[i] != null) { values[i] = Lists.newArrayList(valsets[i]); } }第一步是把descriptor转换为全拼,如下:[IGNORED, NUMERICAL, NUMERICAL, NUMERICAL, NUMERICAL, NUMERICAL, NUMERICAL, NUMERICAL, NUMERICAL, NUMERICAL, LABEL];第二步采用parseString方法去遍历所有的输入文件,看输入文件是否满足descriptor的描述,是则把行数加1,即样本数加1(glass.data的数据,size为214,符合原始数据的样本数);第三步是把标识全部取出来放入values中,values中的值为:[null, null, null, null, null, null, null, null, null, null, [3, 2, 1, 7, 6, 5]]至于values中的最后一个表示标识的为什么不是[1,2,3,5,6,7],是因为在parseString方式中这里的set是HashSet,采用随机存放的方式。该方法返回:
return new Dataset(attrs, values, size, regression);返回一个dataset,这个dataSet中有属性、标识、样本数。
debug方式下看到的dataset如下:
到最后一步,DFUtils.storeWritable(new Configuration(), fPath, dataset);直接把dataset写入了文件,看这个方法:
public static void storeWritable(Configuration conf, Path path, Writable writable) throws IOException里面的最后一个形参是writable的,但是我们传入的是dataset,可以么?看dataset的定义就可以了,看到DataSet是实现了Writable接口的,所以,这个是没有问题的。
分享,成长,快乐
转载请注明blog地址: http://blog.csdn.net/fansy1990