mahout聚类的基本流程
-
基本概念,格式
- SequenceFile文件是一种二制制存储的key-value键值对,是mahout(hadoop)接受的文件输入格式
- mahout进行聚类需要的数据是sequenceFile,以文章聚类来看,seqFile的key,value分别是<docId, docVector>,docId是文章的id,docVector是文章的词频统计向量
- docVector生成过程,首先构造doc的词频向量(数组),之后放到mahout的Vector中,根据需要可以选择DenseVector, RandomAccessSparseVector以及。。。
- 使用
SequenceFile.Writer writer = new SequenceFile.Writer(fs, conf, path, LongWritable.class, VectorWritable.class);
将<docId, docVector>写到sequenceFile中
-
处理原始文件
- 如果输入是目录下的N个文件,每个文件是一片txt的文章,可以使用如下命令转化成mahout需要的输入
- mahout seqdirectory:将文本文件转成SequenceFile文件,对应的源文件是org.apache.mahout.text.SequenceFilesFromDirectory.java
- mahout seq2sparse:将SequenceFile转成向量文件,对应的源文件是org.apache.mahout.vectorizer.SparseVectorsFromSequenceFiles.java
- 如果输入是用户打分数据,文件中每行包括<userId, itemId, rate>,这种格式mahout没办法直接处理,需要自己写一个转换程序
- 由于userId,itemId可能是hash后的值,建议做个映射,做成用户打分矩阵,方便处理
-
之后分别使用下面程序读入打分矩阵,存储Seqfile
public static List<Vector> getMatrix(String dfname, int row, int col) throws IOException{ List<Vector> matrix = new ArrayList<Vector>(); BufferedReader br = new BufferedReader(new FileReader(dfname)); String line = null; String[] svec = null; double[] dvec = new double[col]; int cline = 0; while((line=br.readLine())!=null) { svec = line.split("\t"); if(svec.length!=col) System.exit(COLUMN_ERROR); for(int i=0; i<col; i++){ dvec[i] = Double.valueOf(svec[i]); if(dvec[i]>0.1) System.out.print(dvec[i] + "\t"); } Vector vector = new RandomAccessSparseVector(col); vector.assign(dvec); matrix.add(vector); if(cline>=row) break; else System.out.format("\nline count:%d\t\n", ++cline); } return matrix; } public static void writePointsToFile(List<Vector> points, Configuration conf, Path path) throws IOException { FileSystem fs = FileSystem.get(path.toUri(), conf); SequenceFile.Writer writer = new SequenceFile.Writer(fs, conf, path, LongWritable.class, VectorWritable.class); long recNum = 0; VectorWritable vec = new VectorWritable(); for (Vector point : points) { vec.set(point); writer.append(new LongWritable(recNum++), vec); } writer.close(); }
- 如果输入是目录下的N个文件,每个文件是一片txt的文章,可以使用如下命令转化成mahout需要的输入
-
进行聚类
mahout kmeans -i seqFile(dir) -o output-result -k 3 -dm org.apache.mahout.common.distance.CosineDistanceMeasure -cd 0.001 -x 2 -ow -cl -c kmeans-clusters // mahout Kmeans聚类有两个重要参数:收敛Delta和最大迭代次数.所以有时候改敛时,并还没有达到最大迭代次数
-
显示聚类结果
mahout clusterdump -o clusteranalyze.txt -p kmeans-result/clusteredPoints -dm org.apache.mahout.common.distance.CosineDistanceMeasure -i kmeans-result/clusters-0 -d dictOut
-
输出文件
- 运行mahout kmeas,结果文件夹有clusteredPoints,clusters-N,data,用命令mahout seqdumper仔细看了一下结果文件
- clusteredPoints:存放的是最后聚类的结果,将cluster-id和documents-id都展示出来了,用mahout seqdumper读clusteredPoints结果的key-value类型是(IntWritable,WeightedVectorWritable)
- clusters-N:是第N次聚类的结果,其中n为某类的样本数目,c为各类各属性的中心,r为各类属性的半径。 clusters-N结果类型是(Text,Cluster)
- data:存放的是原始数据,这个文件夹下的文件可以用mahout vectordump来读取,原始数据是向量形式的,其它的都只能用mahout seqdumper来读取,向量文件也可以用mahout seqdumper来读取,只是用vectordump读取出来的是数字结果,没有对应的key,用seqdumper读出来的可以看到key,即对应的url,而value读出来的是一个类描述,而不是数组向量
从数据库上读取数据
mahout没有提供直接生成seqfile的方法,可以使用lucene建立索引,mahout直接读lucene索引生成的vector 1. 使用lucene建立索引 2. 得到向量
mahout lucene.vector --dir index --output vectors --field doctitle --dictOut dictOut --idField doctitle --norm 2 --maxDFPercent 20 --minDF 2 -w TFIDF
其他Utils
- mahout seqdumper:将SequenceFile文件转成可读的文本形式,对应的源文件是org.apache.mahout.utils.SequenceFileDumper.java
- mahout vectordump:将向量文件转成可读的文本形式,对应的源文件是org.apache.mahout.utils.vectors.VectorDumper.java
- 为什么mahout seqdumper可以读任何SequenceFiles文件呢?看了一下源文件,是因为用的reader.getValueClass().newInstance()去读取的