【实验】mahout文本聚类实验
最近用mahout做了一些聚类实验,由于处理的数据比较大(大概有2.4G),因此数据是放在hdfs上在hadoop上实现。这篇博文记录用mahout聚类的命令、以及出现的一些问题和解决方法等。
mahout文本聚类命令
从文本文档中生成向量
首先,需要把文本文档转换成mahout读取是SequenceFile格式。SequenceFile是Hadoop库中的一种文件格式,它由一系列键-值对序列组成。其中,键实现为Hadoop中的WritableComparable,值实现为Writable。实现命令是:
常用的参数及其默认值:
- -ow 输出目录是否被覆盖,默认是不被覆盖
- -chunk 为MB为单位的块大小。处理大文档时,将词典分块存入内存,建议设置为jvm堆的80%(《Mahout实战》书中P121)。
- -xm 执行方法,sequence或者mapreduce,默认是mapreduce
- -prefix 追加到键上的前缀,默认空
- -c 字符集,默认utf-8
需要注意的是,-i是指输入文件的目录,在该目录下,每个文档代表一个文本(已分词,空格隔开),用seqdirectory读取之后,key就是文档名,value就是文档的内容。
接下来,就要用sequenceFile文件生成聚类需要的mahout文件了。mahout的seq2sparse命令从SequenceFile文件中读取文本数据,并将基于词典的向量化程序锁生成的向量写入输出目录中。实现命令:
常用的参数及其默认值:
- -ow 输出目录是否被覆盖,默认是不被覆盖
- -a 所用分析器的雷鸣,默认StandardAnalyzer
- -chunk 同上,默认为100MB
- -wt 加权机制,tf或者tfidf,默认为tfidf
- -s 最小支持度,可以放入词典文件中的词的最小频率,低于该值的被扔掉,默认为2
- -md 最小文档频,默认为1
- -x 最大文档频,默认99
- -ng 文档集合中选出的n-gram最大长度,默认为1
- -ml 当ng>1时生效,最小对数似然比,低于值的词去掉,默认为1.0
- -n 是否归一化,默认是0,不做归一化
- -nr 并行执行的reduce任务的个数
- -seq 生成顺序访问的稀疏向量,如果被设置,输出向量就被创建为SequentialAccessSparseVector,而默认情况下,基于目录的向量化程序创建的是RandomAccessSparseVector。前者在某些算法(如kmeans和SVD)上可获得更高的性能。
Mahout中向量被实现为三个不同的类,每个类都是针对不同场景优化的,分别是:
- DenseVector:一个double类型的数组,大小等于特征数。
- RandomAccessSparseVector:被实现为一个integer型和double型的HashMap,只有非零元素才被分配空间,适合需要对向量值做很多随机插入和更新的算法。
- SequentialAccessSparseVector:实现为两个并列数组,一个是integer型,一个是double型,只保留非零元素,为顺序读取而优化的,适合像kmeans这样需要反复计算向量大小的算法。
kmeans聚类
kmeans的输入数据需要是一系列数值化的系特征向量,即上面所说的SequentialAccessSparseVector。kmeans聚类算法可以通过KMeansClusterer或者KMeansDriver类来执行,前者以in-memory方式对数据点聚类,后者则可以启动一个MapReduce作业来执行,两种当时都可以在hadoop集群上执行,在hdfs上读写数据。
kmeans聚类需要指定k(类簇数)和初始点。初始点可以使用kmeans的随机生成方式,也可以自定义(例如用canopy粗略估计初始点)。
kmeans聚类命令为:
常用的参数及其默认值:
- -c 存储聚类初始点的路径,如果指定了-k,这个路径内容会被重写。
- -k 类簇数,如果自定义-c,该值可以省略。
- -dm 距离度量函数,默认是SquaredEuclideanDistanceMeasure
- -x 最大迭代次数
- -cd optional convergence delta. Default is 0.5
- -ow overwrite output directory if present
- -cl run input vector clustering after computing Canopies
- -xm execution method: sequential or mapreduce
使用MapReduce架构,可以将聚类算法分配到不同的机器上运行,每个mapper处理一个子集,Mapper作业将以流的形式读物输入数据点,并计算出距离这部分点最近的簇。没有Hadoop集群时,也可以直接在java中运行,模拟hadoop单台机器的情形。
聚类结束后,如何查看簇呢?mahout提供了一个clusterdump的命令:
常用的参数及其默认值:
- -df 指定输入文件的格式
- -d vectors中的字典文件
- -s 指定要查看的聚类结果
- -b 指定输出结果的前多少个字节,0表示全部输出。
- -n 输出与该类簇最近的前n个词
在mahout的kmeans聚类过程中,遇到这样几个典型的问题:
Q1:mahout命令设置mapred.map.tasks和mapred.reduce.tasks参数,只有后者起作用,前者不起作用。
A1:这是因为mapper的数量是由mapred.min.split.size决定的,一个文件根据split size分成多少份,就会有多少个mapper。
Q2:怎么设置map数量
A2:map tasks的个数主要是看splitSize,splitSize是这样计算的: splitSize = Math.max(minSize,Math.min(maxSize, blockSize)),分别都是啥意思呢,继续。 minSize就是hadoop配置中的mapred.min.split.size参数,可能配置文件中没有,用-Dmapred.min.split.size就可以,取值0或者1,没有单位。 maxSize就是mapred.max.split.size,mapred-site.xml中也没有,默认值为long类型的最大值,使用时也用-D,单位是B,但是取值时不用写B。 blockSize是hdfs-site.xml中dfs.block.size的值,这个值必须是512的倍数。需要注意的是,如果这个值改变了,那么要重新部署文件才能生效。 参考:http://blog.csdn.net/wf1982/article/details/6672607;
Q3:之前运行是不是出现Java Heap Space错误,而我们的机器足够大,怎么回事?
A3:之前把70w条记录的大文件分成了300来个小文件作为mahout的输入文件,在生成vector时,就会把每个小文件当做一条记录,这样的记录太大了,所以造成jvm溢出。解决办法就是把70w条记录的每条当做一个小文本输入。
总之,遇到问题不用怕。如果是设置问题,就去查看mahout脚本文件(一般位于安装目录的bin/mahout)或者hadoop的配置文件(/etc/hadoop/conf),例如开始遇到的JVM OOM错误就是这样解决的。如果找不到根源,可以根据错误栈,查看源代码,找到出错问题的根源来解决。