Mahout – Clustering (聚类篇)

什么是Mahout?

” Apache Mahout™ project’s goal is to build a scalable machine learning library ”

我来拓展一下:

(1) Mahout 是Apache旗下的开源项目,集成了大量的机器学习算法。

(2) 大部分算法,可以运行在Hadoop上,具有很好的拓展性,使得大数据上的机器学习成为可能。

本篇主要探讨 Mahout 0.9 中的聚类(Clustering)工具的用法。

一、数据准备

Mahout聚类算法的输入为List<Vector>,即需要将每个待聚类的文档,表示为向量形式。

在本文中,我们选择经典的 Reuters21578 文本语料。尝试对新闻内容进行文本聚类。

1、下载数据

1
axel -n 20 http://kdd.ics.uci.edu/databases/reuters21578/reuters21578.tar.gz

2、解压缩数据

1
tar
-xzvf
./reuters21578.tar.gz
./reuters-sgm

解压缩之后,reuters-sgm下,包含了若干*.sgm文件,每个文件中又包含了若干下属结构化文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<REUTERS TOPICS="NO" LEWISSPLIT="TRAIN" CGISPLIT="TRAINING-SET" OLDID="5545" NEWID="2">
<DATE>26-FEB-1987 15:02:20.00</DATE>
<TOPICS></TOPICS>
<PLACES><D>usa</D></PLACES>
<PEOPLE></PEOPLE>
<ORGS></ORGS>
<EXCHANGES></EXCHANGES>
<COMPANIES></COMPANIES>
<UNKNOWN>
F Y
f0708reute
d f BC-STANDARD-OIL-&lt;SRD>-TO   02-26 0082</UNKNOWN>
<TEXT>
<TITLE>STANDARD OIL &lt;SRD> TO FORM FINANCIAL UNIT</TITLE>
<DATELINE>    CLEVELAND, Feb 26 - </DATELINE><BODY>Standard Oil Co and BP North America
Inc said they plan to form a venture to manage the money market
borrowing and investment activities of both companies.
    BP North America is a subsidiary of British Petroleum Co
Plc &lt;BP>, which also owns a 55 pct interest in Standard Oil.
    The venture will be called BP/Standard Financial Trading
and will be operated by Standard Oil under the oversight of a
joint management committee.
 
Reuter
</BODY></TEXT>
</REUTERS>

在下文中,我们主要使用<TITLE>和<BODY>中的文本。即标题+正文。

3、抽取

Mahout中内置了对上述Reuters预料的抽取程序,我们可以直接使用。

1
mahout
org.apache.lucene.benchmark.utils.ExtractReuters
./reuters-sgm
./reuters-out

如上所述,抽取好的结果在./reuters-out文件夹下面,每篇<REUTERS>文档,变成了一个独立的文件。

一共有21578个txt,即数据集中含有21578篇文档:-)

说下命名规则吧,例如:文件名:./reuters-out/reut2-006.sgm-246.txt,表示来自于./reuters-sgm/reut2-006.sgm中的第246篇文档,下标从0开始。

4、转换成SequenceFile

对于传统的文本聚类算法而言,下一步应该是:将文本转化为词的向量空间表示。

然而,不要太着急哦。

由于Mahout运行在Hadoop上,HDFS是为大文件设计的。如果我们把上述21578个txt都拷贝上去,这样是非常不合适的

设想下:假设对1000万篇新闻进行聚类,难道要拷贝1000w个文件么?这会把name node搞挂的。

因此,Mahout采用SequenceFile作为其基本的数据交换格式。

内置的seqdirectory命令(这个命令设计的不合理,应该叫directoryseq才对),可以完成 文本目录->SequenceFile的转换过程。

1
mahout seqdirectory -i file://$(pwd)/reuters-out/ -o file://$(pwd)/reuters-seq/ -c UTF-8 -chunk 64 -xm sequential

上述命令蕴含了2个大坑,在其他文档中均没有仔细说明:
(1) -xm sequential,表示在本地执行,而不是用MapReduce执行。如果是后者,我们势必要将这些小文件上传到HDFS上,那样的话,还要SequenceFile做甚……
(2) 然而seqdirectory在执行的时候,并不因为十本地模式,就在本地文件系统上寻找。而是根据-i -o的文件系统前缀来判断文件位置。也就是说,默认情况,依然十在HDFS上查找的……所以,这个file://的前缀是非常有必要的。

其他2个参数:

  • -c UTF8:编码。
  • -chunk 64:64MB一个Chunk,应该和HDFS的BLOCK保持一致或者倍数关系。

5、转换为向量表示

为了适应多种数据,聚类算法多使用向量空间作为输入数据。

由于我们先前已经得到了处理好的SequenceFile,从这一步开始,就可以在Hadoop上进行啦。

1
hadoop
dfs
-put
reuters-seq
/user/coder4

开始text->Vector的转换:

1
mahout seq2sparse -i /user/coder4/reuters-seq -o /user/coder4/reuters-sparse -ow --weight tfidf --maxDFPercent 85 --namedVector

输入和输出不解释了。在Mahout中的向量类型可以称为sparse。

参数说明如下:

  • -ow( 或 –overwrite):即使输出目录存在,依然覆盖。
  • –weight(或 -wt) tfidf:权重公式,大家都懂的。其他可选的有tf (当LDA时建议使用)。
  • –maxDFPercent(或 -x) 85:过滤高频词,当DF大于85%时,将不在作为词特征输出到向量中。
  • –namedVector (或-nv):向量会输出附加信息。

其他可能有用的选项:

  • –analyzerName(或-a):指定其他分词器。
  • –minDF:最小DF阈值。
  • –minSupport:最小的支持度阈值,默认为2。
  • –maxNGramSize(或-ng):是否创建ngram,默认为1。建议一般设定到2就够了。
  • –minLLR(或 -ml):The minimum Log Likelihood Ratio。默认为1.0。当设定了-ng > 1后,建议设置为较大的值,只过滤有意义的N-Gram。
  • –logNormalize(或 -lnorm):是否对输出向量做Log变换。
  • –norm(或 -n):是否对输出向量做p-norm变换,默认不变换。

看一下产出:

1
2
3
4
5
6
7
8
9
hadoop
dfs
-ls
/user/coder4/reuters-sparse
Found
7
items
/user/coder4/reuters-sparse/df-count
/user/coder4/reuters-sparse/dictionary.file-0
/user/coder4/reuters-sparse/frequency.file-0
/user/coder4/reuters-sparse/tf-vectors
/user/coder4/reuters-sparse/tfidf-vectors
/user/coder4/reuters-sparse/tokenized-documents
/user/coder4/reuters-sparse/wordcount

说明各个文件的用途:

  • dictionary.file-0:词文本 -> 词id(int)的映射。词转化为id,这是常见做法。
  • frequency.file:词id -> 文档集词频(cf)。
  • wordcount(目录): 词文本 -> 文档集词频(cf),这个应该是各种过滤处理之前的信息。
  • df-count(目录): 词id -> 文档频率(df)。
  • tf-vectors、tfidf-vectors (均为目录):词向量,每篇文档一行,格式为{词id:特征值},其中特征值为tf或tfidf。有用采用了内置类型VectorWritable,需要用命令”mahout vectordump -i <path>”查看。
  • tokenized-documents:分词后的文档。

二、KMeans

1、运行K-Means

1
mahout kmeans -i /user/coder4/reuters-sparse/tfidf-vectors -c /user/coder4/reuters-kmeans-clusters -o /user/coder4/reuters-kmeans -k 20 -dm org.apache.mahout.common.distance.CosineDistanceMeasure -x 200 -ow --clustering

参数说明如下:

  • -i:输入为上面产出的tfidf向量。
  • -o:每一轮迭代的结果将输出在这里。
  • -k:几个簇。
  • -c:这是一个神奇的变量。若不设定k,则用这个目录里面的点,作为聚类中心点。否则,随机选择k个点,作为中心点。
  • -dm:距离公式,文本类型推荐用cosine距离。
  • -x :最大迭代次数。
  • –clustering:在mapreduce模式运行。
  • –convergenceDelta:迭代收敛阈值,默认0.5,对于Cosine来说略大。

输出1,初始随机选择的中心点:

1
2
3
hadoop
dfs
-ls
/user/coder4/reuters-kmeans-clusters
Found
1
items
/user/coder4/reuters-kmeans-clusters/part-randomSeed

输出2,聚类过程、结果:

1
2
3
4
5
6
7
hadoop dfs -ls /user/coder4/reuters-kmeans
Found 5 items
/user/coder4/reuters-kmeans/_policy
/user/coder4/reuters-kmeans/clusteredPoints
/user/coder4/reuters-kmeans/clusters-0
/user/coder4/reuters-kmeans/clusters-1
/user/coder4/reuters-kmeans/clusters-2-final

其中,clusters-k(-final)为每次迭代后,簇的20个中心点的信息。

而clusterdPoints,存储了 簇id -> 文档id 的映射。

2、查看簇结果

首先,用clusterdump,来查看k(20)个簇的信息。

1
2
3
4
5
# Get to Local
hadoop
dfs
-get
/user/coder4/reuters-kmeans/
./
hadoop
dfs
-get
/user/coder4/reuters-sparse/
./
# View ..
mahout
clusterdump
-i
/user/coder4/reuters-kmeans/clusters-2-final
-d
./reuters-sparse/dictionary.file-0
-dt
sequencefile
-o
./reuters-kmeans-cluster-dump/
-n
20

要说明的是,clusterdump似乎只能在本地执行……所以先把数据下载到本地吧。

参数说明:

  • -i :我们只看最终迭代生成的簇结果。
  • -d :使用 词 -> 词id 映射,使得我们输出结果中,可以直接显示每个簇,权重最高的词文本,而不是词id。
  • -dt:上面映射类型,由于我们是seqdictionary生成的,so。。
  • -o:最终产出目录
  • -n:每个簇,只输出20个权重最高的词。

看看dump结果吧:

一共有20行,表示20个簇。每行形如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
VL-12722{n=1305 c=[....zorinsky's:0.011, zurich:0.006...], r=[....yuan:1.055, yugoslav:1.027,...]}
 
        Top Terms:
                he                                      =>   3.105303428364896
                said                                    =>  2.8756448350190205
                would                                   =>  2.6413800148214874
                have                                    =>  2.1552908992401942
                government                              =>  1.8426488105364687
                which                                   =>   1.749669294978467
                economic                                =>  1.7431561736768233
                has                                     =>  1.7429241635333532
                prices                                  =>  1.7182022383386604
                oil                                     =>   1.673632335845538
                from                                    =>    1.64287882106971
                u.s                                     =>  1.6223870217115028
                had                                     =>   1.602064758607711
                more                                    =>  1.5874425666999086
                last                                    =>   1.561653600890061
                we                                      =>  1.5274837373316974
                been                                    =>  1.4653439554674872
                year                                    =>  1.4279387724353894
                could                                   =>  1.4152588548331426
                minister                                =>  1.4146991936183066

其中前面的12722是簇的ID,n=1305即簇中有这么多个文档。c向量是簇中心点向量,格式为 词文本:权重(点坐标),r是簇的半径向量,格式为 词文本:半径。

下面的Top Terms是簇中选取出来的特征词。

3、查看聚类结果

其实,聚类结果中,更重要的是,文档被聚到了哪个类。

遗憾的是,在很多资料中,都没有说明这一点。前文我们已经提到了,簇id -> 文档id的结果,保存在了clusteredPoints下面。这也是mahout内置类型存储的。我们可以用seqdumper命令查看。

1
mahout
seqdumper
-i
/user/coder4/reuters-kmeans/clusteredPoints/

其中,-d和-dt的原因同clusterdump。

如果不指定-o,默认输出到屏幕,输出结果为形如:

1
Key: 4255: Value: wt: 1.0 distance: 0.7752480913348985  vec: /reut2-000.sgm-0.txt = [14:4.670, 35:7.545, ... 11278:6.394, 11288:6.731]

其实,这个输出是一个SequenceFile,大家自己写程序也可以读出来的。

Key是ClusterID,上面clusterdump的时候,已经说了。

Value是文档的聚类结果:wt是文档属于簇的概率,对于kmeans总是1.0,/reut2-000.sgm-0.txt就是文档标志啦,前面seqdirectionary的-nv起作用了,再后面的就是这个点的各个词id和权重了。

三、Fuzzy-KMeans

KMeans是一种简单有效的聚类方法,但存在一些缺点。

例如:一个点只能属于一个簇,这种叫做硬聚类。而很多情况下,软聚类才是科学的。例如:《哈利波》属于小说,也属于电影。Fuzzy-Kmeans 通过引入“隶属度”的方式,实现了软聚类。

1、算法简介

详细的介绍转载自:http://home.deib.polimi.it/matteucc/Clustering/tutorial_html/cmeans.html

2、工具用法

执行Fuzzy-KMeans

1
mahout
fkmeans
-i
/user/coder4/reuters-sparse/tfidf-vectors
-c
/user/coder4/reuters-fkmeans-clusters
-o
/user/coder4/reuters-fkmeans
-k
20
-dm
org.apache.mahout.common.distance.CosineDistanceMeasure
-m
1.05
-x
200
-ow
--clustering
--convergenceDelta
0.01

新增算法的柔软参数m,若m接近于1则接近于KMeans;随着m增加,会有越来越多的聚簇重叠(越多的点同时属于多个聚簇)。

3、查看隶属度

如上文所述,在Fuzzy-KMeans中,点以一定的 “概率” 隶属于聚簇。

我们可以用seqdumper查看隶属度:

1
mahout seqdumper -i /user/coder4/reuters-fkmeans/clusteredPoints/

其中的 w: xxx.xxx表示了 隶属度,应当是 0~1之间的数。

四、Canopy

KMeans算法还有一个缺陷: k需要预先给定,在很多场景下,聚类形状都是预先无法知道的,k更无从谈起。因此,往往先用别的算法进行粗略聚类,同时确定初始值,然后再用KMeans算法。

1、算法简介

Canopy Clustering 算法提出于2000年。优点是计算速度快,缺点是结果准确性较低。

尽管如此,其结果依然可以大致描述 聚类中心的位置。因此,常用来与KMeans算法配合使用。

(1) 将数据集向量化得到一个list后放入内存,选择两个距离阈值:T1和T2,其中T1 > T2,对应上图,实线圈为T1,虚线圈为T2,T1和T2的值可以用交叉校验来确定;
(2) 从list中任取一点P,用低计算成本方法快速计算点P与所有Canopy之间的距离(如果当前不存在Canopy,则把点P作为一个Canopy),如果点P与某个Canopy距离在T1以内,则将点P加入到这个Canopy;
(3) 如果点P曾经与某个Canopy的距离在T2以内,则需要把点P从list中删除,这一步是认为点P此时与这个Canopy已经够近了,因此它不可以再做其它Canopy的中心了;
(4) 重复步骤2、3,直到list为空结束。

我再来简单概括一下:阈值T1 > T2。到簇中心点的距离 < T2的点,必须属于本聚簇(硬)。T2 < 到簇中心点距离 < T1的点,可以属于多个聚簇(软)。在后续计算可以被合并。

2、聚类用法

执行Canopy聚类

1
mahout
canopy
-i
/user/coder4/reuters-sparse/tfidf-vectors
-o
/user/coder4/reuters-canopy-centroids
-dm
org.apache.mahout.common.distance.CosineDistanceMeasure
-t1
140
-t2
80
-ow

如上所述,在距离的计算方面,我们选择了欧式距离。阈值T1=150, t2=75。

输出结果,也可以用ClusterDump查看。

1
mahout clusterdump -i /user/coder4/reuters-canopy-centroids/clusters-0-final -d /user/coder4/reuters-sparse/dictionary.file-0 -dt sequencefile -n 20 | vim -

这是一个粗略、大致的结果。在实际应用中,经常被用来作为K-Means的初始聚簇中心,来代替随机选择的K个中心点。这一做法有2个优点:
(1) 无需决定K,因为我们的预设往往是不准的。
(2) 使用Canopy的聚类结果,是一个大致准确的中心点。而随机选择很可能陷入局部最优。

在执行k-means时,若我们不指定k,则会使用-c的路径作为初始聚簇中心点,并跳过随机选择的过程。

1
mahout
kmeans
-i
/user/coder4/reuters-sparse/tfidf-vectors
-c
/user/coder4/reuters-canopy-centroids/clusters-0-final
-o
/user/coder4/reuters-kmeans
-dm
org.apache.mahout.common.distance.CosineDistanceMeasure
-x
200
-ow
--clustering

3、参数选择

最后,我们讨论以下Canopy的参数T1和T2。

  • T1 > T2,具体值是文档及距离计算公式而定。
  • 若T1过大,会使得许多点属于多个Canopy,造成各个簇的中心点距离比较近,使得簇之间的区分不明显。
  • 若T2过大,强标记数据点的数量会增加,从而减少簇个数。
  • 若T2过小,会增加簇的个数,以及计算时间。

网上有人给出了这个做法,仅供参考:

  1. 对数据进行采样。
  2. 计算所有文档之间的平均距离(使用要在Canopy中用的距离公式)。
  3. T1 = 平均距离 * 2;T2 = 平均距离。

上述做法有一定道理,但我认为,以下更加合理:

  1. 对数据进行采样。
  2. 选择一个T2,T1 = 2 * T1。
  3. 进行聚类,并评测聚类效果,可使用k-fold交叉验证。
  4. 迭代选择下一个T2。
  5. 直到找到最优的T1 T2。

五、Spectral

1、谱聚类算法简介

谱聚类算法,参考了文章《Mahout Spectral聚类》

谱聚类算法是一种较为现代的图聚类算法。与K-Means等传统聚类相比,它具有以下特点:

  1. 可以对非欧式距离空间的点进行聚类。传统K-Means将点视为向量,并计算距离。而谱聚类算法要求直接给出两样本间相似度的矩阵。使得一些不便于在欧式空间计算的多特征聚类问题,有了更好的解法。(例如,性别,年龄2个特征,在欧式空间中就没有显著意义)。
  2. 上面的这一更宽泛的约束条件,使得谱聚类对样本空间的形状无限制,并能收敛于全局最优解(无需使用)。

一种典型的谱聚类算法的大致流程是:

  1. 构建样本集的相似度矩阵W。
  2. 对相似度矩阵W进行稀疏化,形成新的相似度矩阵A。
  3. 构建相似度矩阵A的拉普拉斯矩阵L。
  4. 计算拉普拉斯矩阵L的前k个特征值与特征向量,构建特征向量空间。
  5. 将前k个特征向量(列向量)组合成N*k的矩阵,每一行看成k维空间的一个向量,利用K-means或其它经典聚类算法对该矩阵进行聚类。

其中,转化为拉普拉斯矩阵实际是一个降维的过程。正是这一特点,使得谱聚类能够处理超大规模的数据。

2、Mahout中的谱聚类

上文已经提到:

  • 传统K-Means等聚类中,需要将每个样本转化为一个向量。
  • 谱聚类中,则需要直接给一个矩阵,其中存储了任意两个样本之间的相似度。

例如:

在实际应用中,相似矩阵(affinity matrix)是相当稀疏的。所以,Mahout采用了邻接矩阵的输入格式,即(i, j, affinity)表示第i个样本与第j个样本的相似度是affinity。

同时,还需要输入矩阵的维度。原因应该是很好理解的。

如上图中的数据,转化完毕后,就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
0,0,0
0,1,0.6
0,2,0.9
0,3,0.1
0,4,0
0,5,0
1,0,0.6
1,1,0
1,2,0.8
1,3,0
1,4,0
1,5,0
2,0,0.9
2,1,0.8
2,2,0
2,3,0
2,4,0
2,5,0.2
3,0,0.1
3,1,0
3,2,0
3,3,0
3,4,0.7
3,5,0.9
4,0,0
4,1,0
4,2,0
4,3,0.7
4,4,0
4,5,0.8
5,0,0
5,1,0
5,2,0.2
5,3,0.9
5,4,0.8
5,5,0

Mahout中,将谱聚类与KMeans进行了整合,执行命令:

1
mahout
spectralkmeans
-i
/user/coder4/sc-data
-o
/user/coder4/sc-spectral
-d
6
-k
2
-x
100

参数说明:

  • -i :输入的相似度矩阵,邻接矩阵。
  • -k:目标聚成2个簇。
  • -o:聚簇中间结果。
  • -d:相似度矩阵维度为6,也即样本共6个。
  • -x:100,最多迭代100次。
  • -cd:收敛阈值,默认0.5

其他可选参数:

  • -ssvd:使用svd矩阵分解降维。
  • -q:svd相关。

输出的目录结构,与K-Means等相似:

1
2
3
4
5
/user/coder4/sc-spectral/clusters-0/part-eigenSeed
/user/coder4/sc-spectral/kmeans_out/_policy
/user/coder4/sc-spectral/kmeans_out/clusteredPoints
/user/coder4/sc-spectral/kmeans_out/clusters-0
/user/coder4/sc-spectral/kmeans_out/clusters-1-final

说明一下:

  • sc-spectral/clusters-0:初始聚簇。
  • sc-spectral/kmeans_out/clusteredPoints:最终结果,样本->聚簇映射。
  • sc-spectral/kmeans_out/clusters-1-final:最终聚簇的信息。

先看一下聚簇映射:

1
2
3
4
5
6
7
8
9
mahout
seqdumper
-i
/user/coder4/sc-spectral/kmeans_out/clusteredPoints
# Output
Key:
1:
Value:
wt:
1.0
distance:
0.48931489242582005  vec:
0
=
[0.188,
-0.982]
Key:
1:
Value:
wt:
1.0
distance:
0.7493112387139778  vec:
1
=
[0.915,
0.404]
Key:
0:
Value:
wt:
1.0
distance:
0.04922584424967602  vec:
2
=
[-0.902,
0.432]
Key:
1:
Value:
wt:
1.0
distance:
0.2811018866117967  vec:
3
=
[0.465,
-0.885]
Key:
1:
Value:
wt:
1.0
distance:
1.1021255831766634  vec:
4
=
[0.748,
0.664]
Key:
0:
Value:
wt:
1.0
distance:
0.012306461062418617  vec:
5
=
[-0.994,
0.112]
Count:
6

如上所示,这个顺序,是按照输入样本顺序来的。Key 1表示属于第2个簇,0表示第1个簇。distance是点与簇的相似距离。

然后来看一下簇中心:

1
mahout clusterdump -i /user/coder4/sc-spectral/kmeans_out/clusters-1-final

输出结果:

1
2
3
4
5
6
7
8
9
10
VL-0{n=3
c=[-0.963,
0.219]
r=[0.043,
0.151]}
        Weight
:
[props
-
optional]:  Point:
        1.0
:
[distance=0.04922584424967602]:
2
=
[-0.902,
0.432]
        1.0
:
[distance=0.012306461062418617]:
5
=
[-0.994,
0.112]
VL-1{n=5
c=[0.501,
-0.356]
r=[0.293,
0.732]}
        Weight
:
[props
-
optional]:  Point:
        1.0
:
[distance=0.48931489242582005]:
0
=
[0.188,
-0.982]
        1.0
:
[distance=0.7493112387139778]:
1
=
[0.915,
0.404]
        1.0
:
[distance=0.2811018866117967]:
3
=
[0.465,
-0.885]
        1.0
:
[distance=1.1021255831766634]:
4
=
[0.748,
0.664]

于 K-Means一样,VL-XX是簇名称,n代表簇中含有几个元素。c是簇中心,r是簇半径。

然而奇怪的是,我们可以发现,上面的n都是错的,而下面簇中点的打印是对的不知道是什么Bug…

六、LDA

LDA是一种主题模型,它是一种考虑了词贡献的,较为高级的“聚类”算法,主要功能为:

  1. 给定主题数k,输出文档属于每个主题的概率(越大表示越贴近该主题)。
  2. 输出每个主题中,权重最大的几个词。相当于传统聚类之后的Tag。

关于算法、原理方面,本文就不做过多的介绍了,感兴趣的可以查看相关论文。

考虑到LDA的特性,提取特征的时候,我们需要使用tf而非tfidf:

1
mahout seq2sparse -i /user/coder4/reuters-seq -o /user/coder4/reuters-sparse -ow --weight tf --maxDFPercent 50 --namedVector

Mahout实现的LDA有个大坑:tf的vector,词必须是Ingeter类型,即要我们把word转换成wordid。

1
mahout
rowid
-i
/user/coder4/reuters-sparse/tf-vectors
-o
/user/coder4/reuters-cvb-vectoers

生成的有2个子目录,我们只用下面这个matrix:

1
2
3
4
hadoop dfs -ls /user/coder4/reuters-cvb-vectoers
Found 2 items
/user/coder4/reuters-cvb-vectoers/docIndex
/user/coder4/reuters-cvb-vectoers/matrix

LDA训练:

1
mahout
cvb
-i
/user/coder4/reuters-cvb-vectoers/matrix
-dict
/user/coder4/reuters-sparse/dictionary.file-0
-dt
/user/coder4/reuters-lda-documents
-o
/user/coder4/reuters-lda
-k
20
-x
100
-ow
-nt
41807

上述参数,说明一下:

  • -k 主题数20
  • -dt:输出的?
  • -o:输出的?
  • -x:迭代100次,其实对于LDA,1000~2000次是比较合理的。
  • -nt:词的数量,即dictionary.file-0的大小。

PS:Mahout这个LDA,执行效率真心不高,也可能是我的数据太小,机器太少。

文档->主题的概率

Shell

1
mahout seqdumper -i /user/coder4/reuters-lda-documents

输出共21578行,代表了文档集合中的所有文档。

  • Key是文档id,与文件的对应关系可以在/user/coder4/reuters-cvb-vectoers/docIndex中查看。
  • Value是文档属于Topic 0~19的概率。按照值Sort一下,就能知道文档属于哪个主题的概率最大。
1
Key:
0:
Value:
{0:6.598480107368694E-4,1:0.0011146789596809571,2:0.031866546260962164,3:3.0247890790462996E-4,4:0.03744780092635172,5:0.0013729445191034027,6:0.15611424913721567,7:0.0012755723292771565,8:5.7202115335459274E-5,9:7.1215044519594E-4,10:5.743926686503027E-4,11:0.0031917247557773252,12:0.6581022112710254,13:0.00817715737882251,14:3.1885030061811875E-4,15:0.011053824003897846,16:0.031248628641714408,17:0.03558264264919826,18:0.01996534695313223,19:8.617497653994247E-4}

主题->词的概率

Shell

1
mahout seqdumper -i /user/coder4/reuters-lda
  • 一共有20行有效输出,Key 0~19,代表了20个主题。
  • 每个Value中有41806个词的权重。表示了词属于当前主题的权重。

本来有个LDAPrintTopics,可以直接打印Topic对应的词的,但是年久失修,已经不能用在新版的cvb的LDA上了。大家可以写程序对上免每个Topic中词的权重进行排序,从而获得每个主题的代表词。

什么是Mahout?

” Apache Mahout™ project’s goal is to build a scalable machine learning library ”

我来拓展一下:
(1) Mahout 是Apache旗下的开源项目,集成了大量的机器学习算法。
(2) 大部分算法,可以运行在Hadoop上,具有很好的拓展性,使得大数据上的机器学习成为可能。

本篇主要探讨 Mahout 0.9 中的聚类(Clustering)工具的用法。

一、数据准备

Mahout聚类算法的输入为List<Vector>,即需要将每个待聚类的文档,表示为向量形式。

在本文中,我们选择经典的 Reuters21578 文本语料。尝试对新闻内容进行文本聚类。

1、下载数据

1
axel
-n
20
http://kdd.ics.uci.edu/databases/reuters21578/reuters21578.tar.gz

2、解压缩数据

1
tar -xzvf ./reuters21578.tar.gz ./reuters-sgm

解压缩之后,reuters-sgm下,包含了若干*.sgm文件,每个文件中又包含了若干下属结构化文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<REUTERS
TOPICS="NO"
LEWISSPLIT="TRAIN"
CGISPLIT="TRAINING-SET"
OLDID="5545"
NEWID="2">
<DATE>26-FEB-1987
15:02:20.00</DATE>
<TOPICS></TOPICS>
<PLACES><D>usa</D></PLACES>
<PEOPLE></PEOPLE>
<ORGS></ORGS>
<EXCHANGES></EXCHANGES>
<COMPANIES></COMPANIES>
<UNKNOWN>
&#5;&#5;&#5;F
Y
&#22;&#22;&#1;f0708&#31;reute
d
f
BC-STANDARD-OIL-&lt;SRD>-TO  
02-26
0082</UNKNOWN>
<TEXT>&#2;
<TITLE>STANDARD
OIL
&lt;SRD>
TO
FORM
FINANCIAL
UNIT</TITLE>
<DATELINE>    CLEVELAND,
Feb
26
-
</DATELINE><BODY>Standard
Oil
Co
and
BP
North
America
Inc
said
they
plan
to
form
a
venture
to
manage
the
money
market
borrowing
and
investment
activities
of
both
companies.
    BP
North
America
is
a
subsidiary
of
British
Petroleum
Co
Plc
&lt;BP>,
which
also
owns
a
55
pct
interest
in
Standard
Oil.
    The
venture
will
be
called
BP/Standard
Financial
Trading
and
will
be
operated
by
Standard
Oil
under
the
oversight
of
a
joint
management
committee.
 
Reuter
&#3;</BODY></TEXT>
</REUTERS>

在下文中,我们主要使用<TITLE>和<BODY>中的文本。即标题+正文。

3、抽取

Mahout中内置了对上述Reuters预料的抽取程序,我们可以直接使用。

1
mahout org.apache.lucene.benchmark.utils.ExtractReuters ./reuters-sgm ./reuters-out

如上所述,抽取好的结果在./reuters-out文件夹下面,每篇<REUTERS>文档,变成了一个独立的文件。

一共有21578个txt,即数据集中含有21578篇文档:-)

说下命名规则吧,例如:文件名:./reuters-out/reut2-006.sgm-246.txt,表示来自于./reuters-sgm/reut2-006.sgm中的第246篇文档,下标从0开始。

4、转换成SequenceFile

对于传统的文本聚类算法而言,下一步应该是:将文本转化为词的向量空间表示。

然而,不要太着急哦。

由于Mahout运行在Hadoop上,HDFS是为大文件设计的。如果我们把上述21578个txt都拷贝上去,这样是非常不合适的

设想下:假设对1000万篇新闻进行聚类,难道要拷贝1000w个文件么?这会把name node搞挂的。

因此,Mahout采用SequenceFile作为其基本的数据交换格式。

内置的seqdirectory命令(这个命令设计的不合理,应该叫directoryseq才对),可以完成 文本目录->SequenceFile的转换过程。

1
mahout
seqdirectory
-i
file://$(pwd)/reuters-out/
-o file://$(pwd)/reuters-seq/ -c UTF-8 -chunk 64 -xm sequential

上述命令蕴含了2个大坑,在其他文档中均没有仔细说明:

(1) -xm sequential,表示在本地执行,而不是用MapReduce执行。如果是后者,我们势必要将这些小文件上传到HDFS上,那样的话,还要SequenceFile做甚……

(2) 然而seqdirectory在执行的时候,并不因为十本地模式,就在本地文件系统上寻找。而是根据-i -o的文件系统前缀来判断文件位置。也就是说,默认情况,依然十在HDFS上查找的……所以,这个file://的前缀是非常有必要的。

其他2个参数:

  • -c UTF8:编码。
  • -chunk 64:64MB一个Chunk,应该和HDFS的BLOCK保持一致或者倍数关系。

5、转换为向量表示

为了适应多种数据,聚类算法多使用向量空间作为输入数据。

由于我们先前已经得到了处理好的SequenceFile,从这一步开始,就可以在Hadoop上进行啦。

1
hadoop dfs -put reuters-seq /user/coder4

开始text->Vector的转换:

1
mahout
seq2sparse
-i
/user/coder4/reuters-seq
-o
/user/coder4/reuters-sparse
-ow
--weight
tfidf
--maxDFPercent
85
--namedVector

输入和输出不解释了。在Mahout中的向量类型可以称为sparse。

参数说明如下:

  • -ow( 或 –overwrite):即使输出目录存在,依然覆盖。
  • –weight(或 -wt) tfidf:权重公式,大家都懂的。其他可选的有tf (当LDA时建议使用)。
  • –maxDFPercent(或 -x) 85:过滤高频词,当DF大于85%时,将不在作为词特征输出到向量中。
  • –namedVector (或-nv):向量会输出附加信息。

其他可能有用的选项:

  • –analyzerName(或-a):指定其他分词器。
  • –minDF:最小DF阈值。
  • –minSupport:最小的支持度阈值,默认为2。
  • –maxNGramSize(或-ng):是否创建ngram,默认为1。建议一般设定到2就够了。
  • –minLLR(或 -ml):The minimum Log Likelihood Ratio。默认为1.0。当设定了-ng > 1后,建议设置为较大的值,只过滤有意义的N-Gram。
  • –logNormalize(或 -lnorm):是否对输出向量做Log变换。
  • –norm(或 -n):是否对输出向量做p-norm变换,默认不变换。

看一下产出:

1
2
3
4
5
6
7
8
9
hadoop dfs -ls /user/coder4/reuters-sparse
Found 7 items
/user/coder4/reuters-sparse/df-count
/user/coder4/reuters-sparse/dictionary.file-0
/user/coder4/reuters-sparse/frequency.file-0
/user/coder4/reuters-sparse/tf-vectors
/user/coder4/reuters-sparse/tfidf-vectors
/user/coder4/reuters-sparse/tokenized-documents
/user/coder4/reuters-sparse/wordcount

说明各个文件的用途:

  • dictionary.file-0:词文本 -> 词id(int)的映射。词转化为id,这是常见做法。
  • frequency.file:词id -> 文档集词频(cf)。
  • wordcount(目录): 词文本 -> 文档集词频(cf),这个应该是各种过滤处理之前的信息。
  • df-count(目录): 词id -> 文档频率(df)。
  • tf-vectors、tfidf-vectors (均为目录):词向量,每篇文档一行,格式为{词id:特征值},其中特征值为tf或tfidf。有用采用了内置类型VectorWritable,需要用命令”mahout vectordump -i <path>”查看。
  • tokenized-documents:分词后的文档。

二、KMeans

1、运行K-Means

1
mahout
kmeans
-i
/user/coder4/reuters-sparse/tfidf-vectors
-c
/user/coder4/reuters-kmeans-clusters
-o
/user/coder4/reuters-kmeans
-k
20
-dm
org.apache.mahout.common.distance.CosineDistanceMeasure
-x
200
-ow
--clustering

参数说明如下:

  • -i:输入为上面产出的tfidf向量。
  • -o:每一轮迭代的结果将输出在这里。
  • -k:几个簇。
  • -c:这是一个神奇的变量。若不设定k,则用这个目录里面的点,作为聚类中心点。否则,随机选择k个点,作为中心点。
  • -dm:距离公式,文本类型推荐用cosine距离。
  • -x :最大迭代次数。
  • –clustering:在mapreduce模式运行。
  • –convergenceDelta:迭代收敛阈值,默认0.5,对于Cosine来说略大。

输出1,初始随机选择的中心点:

1
2
3
hadoop dfs -ls /user/coder4/reuters-kmeans-clusters
Found 1 items
/user/coder4/reuters-kmeans-clusters/part-randomSeed

输出2,聚类过程、结果:

1
2
3
4
5
6
7
hadoop
dfs
-ls
/user/coder4/reuters-kmeans
Found
5
items
/user/coder4/reuters-kmeans/_policy
/user/coder4/reuters-kmeans/clusteredPoints
/user/coder4/reuters-kmeans/clusters-0
/user/coder4/reuters-kmeans/clusters-1
/user/coder4/reuters-kmeans/clusters-2-final

其中,clusters-k(-final)为每次迭代后,簇的20个中心点的信息。

而clusterdPoints,存储了 簇id -> 文档id 的映射。

2、查看簇结果

首先,用clusterdump,来查看k(20)个簇的信息。

1
2
3
4
5
# Get to Local
hadoop dfs -get /user/coder4/reuters-kmeans/ ./
hadoop dfs -get /user/coder4/reuters-sparse/ ./
# View ..
mahout clusterdump -i /user/coder4/reuters-kmeans/clusters-2-final -d ./reuters-sparse/dictionary.file-0 -dt sequencefile -o ./reuters-kmeans-cluster-dump/ -n 20

要说明的是,clusterdump似乎只能在本地执行……所以先把数据下载到本地吧。

参数说明:

  • -i :我们只看最终迭代生成的簇结果。
  • -d :使用 词 -> 词id 映射,使得我们输出结果中,可以直接显示每个簇,权重最高的词文本,而不是词id。
  • -dt:上面映射类型,由于我们是seqdictionary生成的,so。。
  • -o:最终产出目录
  • -n:每个簇,只输出20个权重最高的词。

看看dump结果吧:

一共有20行,表示20个簇。每行形如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
VL-12722{n=1305
c=[....zorinsky's:0.011,
zurich:0.006...],
r=[....yuan:1.055,
yugoslav:1.027,...]}
 
        Top
Terms:
                he                                      =>  
3.105303428364896
                said                                    =>  2.8756448350190205
                would                                  
=>  2.6413800148214874
                have                                    =>  2.1552908992401942
                government                              =>  1.8426488105364687
                which                                  
=>  
1.749669294978467
                economic                                =>  1.7431561736768233
                has                                    
=>  1.7429241635333532
                prices                                  =>  1.7182022383386604
                oil                                    
=>  
1.673632335845538
                from                                    =>    1.64287882106971
                u.s                                    
=>  1.6223870217115028
                had                                    
=>  
1.602064758607711
                more                                    =>  1.5874425666999086
                last                                    =>  
1.561653600890061
                we                                      =>  1.5274837373316974
                been                                    =>  1.4653439554674872
                year                                    =>  1.4279387724353894
                could                                  
=>  1.4152588548331426
                minister                                =>  1.4146991936183066

其中前面的12722是簇的ID,n=1305即簇中有这么多个文档。c向量是簇中心点向量,格式为 词文本:权重(点坐标),r是簇的半径向量,格式为 词文本:半径。

下面的Top Terms是簇中选取出来的特征词。

3、查看聚类结果

其实,聚类结果中,更重要的是,文档被聚到了哪个类。

遗憾的是,在很多资料中,都没有说明这一点。前文我们已经提到了,簇id -> 文档id的结果,保存在了clusteredPoints下面。这也是mahout内置类型存储的。我们可以用seqdumper命令查看。

1
mahout seqdumper -i /user/coder4/reuters-kmeans/clusteredPoints/

其中,-d和-dt的原因同clusterdump。

如果不指定-o,默认输出到屏幕,输出结果为形如:

1
Key:
4255:
Value:
wt:
1.0
distance:
0.7752480913348985  vec:
/reut2-000.sgm-0.txt
=
[14:4.670,
35:7.545,
...
11278:6.394,
11288:6.731]

其实,这个输出是一个SequenceFile,大家自己写程序也可以读出来的。

Key是ClusterID,上面clusterdump的时候,已经说了。

Value是文档的聚类结果:wt是文档属于簇的概率,对于kmeans总是1.0,/reut2-000.sgm-0.txt就是文档标志啦,前面seqdirectionary的-nv起作用了,再后面的就是这个点的各个词id和权重了。

三、Fuzzy-KMeans

KMeans是一种简单有效的聚类方法,但存在一些缺点。

例如:一个点只能属于一个簇,这种叫做硬聚类。而很多情况下,软聚类才是科学的。例如:《哈利波》属于小说,也属于电影。Fuzzy-Kmeans 通过引入“隶属度”的方式,实现了软聚类。

1、算法简介

详细的介绍转载自:http://home.deib.polimi.it/matteucc/Clustering/tutorial_html/cmeans.html

2、工具用法

执行Fuzzy-KMeans

1
mahout fkmeans -i /user/coder4/reuters-sparse/tfidf-vectors -c /user/coder4/reuters-fkmeans-clusters -o /user/coder4/reuters-fkmeans -k 20 -dm org.apache.mahout.common.distance.CosineDistanceMeasure -m 1.05 -x 200 -ow --clustering --convergenceDelta 0.01

新增算法的柔软参数m,若m接近于1则接近于KMeans;随着m增加,会有越来越多的聚簇重叠(越多的点同时属于多个聚簇)。

3、查看隶属度

如上文所述,在Fuzzy-KMeans中,点以一定的 “概率” 隶属于聚簇。

我们可以用seqdumper查看隶属度:

1
mahout
seqdumper
-i
/user/coder4/reuters-fkmeans/clusteredPoints/

其中的 w: xxx.xxx表示了 隶属度,应当是 0~1之间的数。

四、Canopy

KMeans算法还有一个缺陷: k需要预先给定,在很多场景下,聚类形状都是预先无法知道的,k更无从谈起。因此,往往先用别的算法进行粗略聚类,同时确定初始值,然后再用KMeans算法。

1、算法简介

Canopy Clustering 算法提出于2000年。优点是计算速度快,缺点是结果准确性较低。

尽管如此,其结果依然可以大致描述 聚类中心的位置。因此,常用来与KMeans算法配合使用。

(1) 将数据集向量化得到一个list后放入内存,选择两个距离阈值:T1和T2,其中T1 > T2,对应上图,实线圈为T1,虚线圈为T2,T1和T2的值可以用交叉校验来确定;

(2) 从list中任取一点P,用低计算成本方法快速计算点P与所有Canopy之间的距离(如果当前不存在Canopy,则把点P作为一个Canopy),如果点P与某个Canopy距离在T1以内,则将点P加入到这个Canopy;

(3) 如果点P曾经与某个Canopy的距离在T2以内,则需要把点P从list中删除,这一步是认为点P此时与这个Canopy已经够近了,因此它不可以再做其它Canopy的中心了;

(4) 重复步骤2、3,直到list为空结束。

我再来简单概括一下:阈值T1 > T2。到簇中心点的距离 < T2的点,必须属于本聚簇(硬)。T2 < 到簇中心点距离 < T1的点,可以属于多个聚簇(软)。在后续计算可以被合并。

2、聚类用法

执行Canopy聚类

1
mahout canopy -i /user/coder4/reuters-sparse/tfidf-vectors -o /user/coder4/reuters-canopy-centroids -dm org.apache.mahout.common.distance.CosineDistanceMeasure -t1 140 -t2 80 -ow

如上所述,在距离的计算方面,我们选择了欧式距离。阈值T1=150, t2=75。

输出结果,也可以用ClusterDump查看。

1
mahout
clusterdump
-i
/user/coder4/reuters-canopy-centroids/clusters-0-final
-d
/user/coder4/reuters-sparse/dictionary.file-0
-dt
sequencefile
-n
20
|
vim
-

这是一个粗略、大致的结果。在实际应用中,经常被用来作为K-Means的初始聚簇中心,来代替随机选择的K个中心点。这一做法有2个优点:

(1) 无需决定K,因为我们的预设往往是不准的。

(2) 使用Canopy的聚类结果,是一个大致准确的中心点。而随机选择很可能陷入局部最优。

在执行k-means时,若我们不指定k,则会使用-c的路径作为初始聚簇中心点,并跳过随机选择的过程。

1
mahout kmeans -i /user/coder4/reuters-sparse/tfidf-vectors -c /user/coder4/reuters-canopy-centroids/clusters-0-final -o /user/coder4/reuters-kmeans -dm org.apache.mahout.common.distance.CosineDistanceMeasure -x 200 -ow --clustering

3、参数选择

最后,我们讨论以下Canopy的参数T1和T2。

  • T1 > T2,具体值是文档及距离计算公式而定。
  • 若T1过大,会使得许多点属于多个Canopy,造成各个簇的中心点距离比较近,使得簇之间的区分不明显。
  • 若T2过大,强标记数据点的数量会增加,从而减少簇个数。
  • 若T2过小,会增加簇的个数,以及计算时间。

网上有人给出了这个做法,仅供参考:

  1. 对数据进行采样。
  2. 计算所有文档之间的平均距离(使用要在Canopy中用的距离公式)。
  3. T1 = 平均距离 * 2;T2 = 平均距离。

上述做法有一定道理,但我认为,以下更加合理:

  1. 对数据进行采样。
  2. 选择一个T2,T1 = 2 * T1。
  3. 进行聚类,并评测聚类效果,可使用k-fold交叉验证。
  4. 迭代选择下一个T2。
  5. 直到找到最优的T1 T2。

五、Spectral

1、谱聚类算法简介

谱聚类算法,参考了文章《Mahout Spectral聚类》

谱聚类算法是一种较为现代的图聚类算法。与K-Means等传统聚类相比,它具有以下特点:

  1. 可以对非欧式距离空间的点进行聚类。传统K-Means将点视为向量,并计算距离。而谱聚类算法要求直接给出两样本间相似度的矩阵。使得一些不便于在欧式空间计算的多特征聚类问题,有了更好的解法。(例如,性别,年龄2个特征,在欧式空间中就没有显著意义)。
  2. 上面的这一更宽泛的约束条件,使得谱聚类对样本空间的形状无限制,并能收敛于全局最优解(无需使用)。

一种典型的谱聚类算法的大致流程是:

  1. 构建样本集的相似度矩阵W。
  2. 对相似度矩阵W进行稀疏化,形成新的相似度矩阵A。
  3. 构建相似度矩阵A的拉普拉斯矩阵L。
  4. 计算拉普拉斯矩阵L的前k个特征值与特征向量,构建特征向量空间。
  5. 将前k个特征向量(列向量)组合成N*k的矩阵,每一行看成k维空间的一个向量,利用K-means或其它经典聚类算法对该矩阵进行聚类。

其中,转化为拉普拉斯矩阵实际是一个降维的过程。正是这一特点,使得谱聚类能够处理超大规模的数据。

2、Mahout中的谱聚类

上文已经提到:

  • 传统K-Means等聚类中,需要将每个样本转化为一个向量。
  • 谱聚类中,则需要直接给一个矩阵,其中存储了任意两个样本之间的相似度。

例如:

在实际应用中,相似矩阵(affinity matrix)是相当稀疏的。所以,Mahout采用了邻接矩阵的输入格式,即(i, j, affinity)表示第i个样本与第j个样本的相似度是affinity。

同时,还需要输入矩阵的维度。原因应该是很好理解的。

如上图中的数据,转化完毕后,就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
0,0,0
0,1,0.6
0,2,0.9
0,3,0.1
0,4,0
0,5,0
1,0,0.6
1,1,0
1,2,0.8
1,3,0
1,4,0
1,5,0
2,0,0.9
2,1,0.8
2,2,0
2,3,0
2,4,0
2,5,0.2
3,0,0.1
3,1,0
3,2,0
3,3,0
3,4,0.7
3,5,0.9
4,0,0
4,1,0
4,2,0
4,3,0.7
4,4,0
4,5,0.8
5,0,0
5,1,0
5,2,0.2
5,3,0.9
5,4,0.8
5,5,0

Mahout中,将谱聚类与KMeans进行了整合,执行命令:

1
mahout spectralkmeans -i /user/coder4/sc-data -o /user/coder4/sc-spectral -d 6 -k 2 -x 100

参数说明:

  • -i :输入的相似度矩阵,邻接矩阵。
  • -k:目标聚成2个簇。
  • -o:聚簇中间结果。
  • -d:相似度矩阵维度为6,也即样本共6个。
  • -x:100,最多迭代100次。
  • -cd:收敛阈值,默认0.5

其他可选参数:

  • -ssvd:使用svd矩阵分解降维。
  • -q:svd相关。

输出的目录结构,与K-Means等相似:

1
2
3
4
5
/user/coder4/sc-spectral/clusters-0/part-eigenSeed
/user/coder4/sc-spectral/kmeans_out/_policy
/user/coder4/sc-spectral/kmeans_out/clusteredPoints
/user/coder4/sc-spectral/kmeans_out/clusters-0
/user/coder4/sc-spectral/kmeans_out/clusters-1-final

说明一下:

  • sc-spectral/clusters-0:初始聚簇。
  • sc-spectral/kmeans_out/clusteredPoints:最终结果,样本->聚簇映射。
  • sc-spectral/kmeans_out/clusters-1-final:最终聚簇的信息。

先看一下聚簇映射:

1
2
3
4
5
6
7
8
9
mahout seqdumper -i /user/coder4/sc-spectral/kmeans_out/clusteredPoints
# Output
Key: 1: Value: wt: 1.0 distance: 0.48931489242582005  vec: 0 = [0.188, -0.982]
Key: 1: Value: wt: 1.0 distance: 0.7493112387139778  vec: 1 = [0.915, 0.404]
Key: 0: Value: wt: 1.0 distance: 0.04922584424967602  vec: 2 = [-0.902, 0.432]
Key: 1: Value: wt: 1.0 distance: 0.2811018866117967  vec: 3 = [0.465, -0.885]
Key: 1: Value: wt: 1.0 distance: 1.1021255831766634  vec: 4 = [0.748, 0.664]
Key: 0: Value: wt: 1.0 distance: 0.012306461062418617  vec: 5 = [-0.994, 0.112]
Count: 6

如上所示,这个顺序,是按照输入样本顺序来的。Key 1表示属于第2个簇,0表示第1个簇。distance是点与簇的相似距离。

然后来看一下簇中心:

1
mahout
clusterdump
-i
/user/coder4/sc-spectral/kmeans_out/clusters-1-final

输出结果:

1
2
3
4
5
6
7
8
9
10
VL-0{n=3 c=[-0.963, 0.219] r=[0.043, 0.151]}
        Weight : [props - optional]:  Point:
        1.0 : [distance=0.04922584424967602]: 2 = [-0.902, 0.432]
        1.0 : [distance=0.012306461062418617]: 5 = [-0.994, 0.112]
VL-1{n=5 c=[0.501, -0.356] r=[0.293, 0.732]}
        Weight : [props - optional]:  Point:
        1.0 : [distance=0.48931489242582005]: 0 = [0.188, -0.982]
        1.0 : [distance=0.7493112387139778]: 1 = [0.915, 0.404]
        1.0 : [distance=0.2811018866117967]: 3 = [0.465, -0.885]
        1.0 : [distance=1.1021255831766634]: 4 = [0.748, 0.664]

于 K-Means一样,VL-XX是簇名称,n代表簇中含有几个元素。c是簇中心,r是簇半径。

然而奇怪的是,我们可以发现,上面的n都是错的,而下面簇中点的打印是对的不知道是什么Bug…

六、LDA

LDA是一种主题模型,它是一种考虑了词贡献的,较为高级的“聚类”算法,主要功能为:

  1. 给定主题数k,输出文档属于每个主题的概率(越大表示越贴近该主题)。
  2. 输出每个主题中,权重最大的几个词。相当于传统聚类之后的Tag。

关于算法、原理方面,本文就不做过多的介绍了,感兴趣的可以查看相关论文。

考虑到LDA的特性,提取特征的时候,我们需要使用tf而非tfidf:

1
mahout
seq2sparse
-i
/user/coder4/reuters-seq
-o
/user/coder4/reuters-sparse
-ow
--weight
tf
--maxDFPercent
50
--namedVector

Mahout实现的LDA有个大坑:tf的vector,词必须是Ingeter类型,即要我们把word转换成wordid。

1
mahout rowid -i /user/coder4/reuters-sparse/tf-vectors -o /user/coder4/reuters-cvb-vectoers

生成的有2个子目录,我们只用下面这个matrix:

1
2
3
4
hadoop
dfs
-ls
/user/coder4/reuters-cvb-vectoers
Found
2
items
/user/coder4/reuters-cvb-vectoers/docIndex
/user/coder4/reuters-cvb-vectoers/matrix

LDA训练:

1
mahout cvb -i /user/coder4/reuters-cvb-vectoers/matrix -dict /user/coder4/reuters-sparse/dictionary.file-0 -dt /user/coder4/reuters-lda-documents -o /user/coder4/reuters-lda -k 20 -x 100 -ow -nt 41807

上述参数,说明一下:

  • -k 主题数20
  • -dt:输出的?
  • -o:输出的?
  • -x:迭代100次,其实对于LDA,1000~2000次是比较合理的。
  • -nt:词的数量,即dictionary.file-0的大小。

PS:Mahout这个LDA,执行效率真心不高,也可能是我的数据太小,机器太少。

文档->主题的概率

Shell

1
mahout
seqdumper
-i
/user/coder4/reuters-lda-documents

输出共21578行,代表了文档集合中的所有文档。

  • Key是文档id,与文件的对应关系可以在/user/coder4/reuters-cvb-vectoers/docIndex中查看。
  • Value是文档属于Topic 0~19的概率。按照值Sort一下,就能知道文档属于哪个主题的概率最大。
1
Key: 0: Value: {0:6.598480107368694E-4,1:0.0011146789596809571,2:0.031866546260962164,3:3.0247890790462996E-4,4:0.03744780092635172,5:0.0013729445191034027,6:0.15611424913721567,7:0.0012755723292771565,8:5.7202115335459274E-5,9:7.1215044519594E-4,10:5.743926686503027E-4,11:0.0031917247557773252,12:0.6581022112710254,13:0.00817715737882251,14:3.1885030061811875E-4,15:0.011053824003897846,16:0.031248628641714408,17:0.03558264264919826,18:0.01996534695313223,19:8.617497653994247E-4}

主题->词的概率

Shell

1
mahout
seqdumper
-i
/user/coder4/reuters-lda
  • 一共有20行有效输出,Key 0~19,代表了20个主题。
  • 每个Value中有41806个词的权重。表示了词属于当前主题的权重。

本来有个LDAPrintTopics,可以直接打印Topic对应的词的,但是年久失修,已经不能用在新版的cvb的LDA上了。大家可以写程序对上免每个Topic中词的权重进行排序,从而获得每个主题的代表词。

Mahout0.9 – Clustering (聚类篇)的更多相关文章

  1. 【原】Coursera—Andrew Ng机器学习—课程笔记 Lecture 13—Clustering 聚类

    Lecture 13 聚类 Clustering 13.1 无监督学习简介  Unsupervised Learning Introduction 现在开始学习第一个无监督学习算法:聚类.我们的数据没 ...

  2. 基于K-means Clustering聚类算法对电商商户进行级别划分(含Octave仿真)

    在从事电商做频道运营时,每到关键时间节点,大促前,季度末等等,我们要做的一件事情就是品牌池打分,更新所有店铺的等级.例如,所以的商户分入SKA,KA,普通店铺,新店铺这4个级别,对于不同级别的商户,会 ...

  3. 机器学习之&amp;&amp;Andrew Ng课程复习--- 聚类——Clustering

    第十三章.聚类--Clustering ******************************************************************************** ...

  4. 聚类(一)——Kmeans

    Clustering 聚类K-means 聚类是机器学习和数据挖掘领域的主要研究方向之一,它是一种无监督学习算法,小编研究生时期的主要研究方向是“数据流自适应聚类算法”,所以对聚类算法有比较深刻的理解 ...

  5. 聚类——密度聚类DBSCAN

    Clustering 聚类 密度聚类——DBSCAN 前面我们已经介绍了两种聚类算法:k-means和谱聚类.今天,我们来介绍一种基于密度的聚类算法——DBSCAN,它是最经典的密度聚类算法,是很多算 ...

  6. Mahout之Canopy Clustering深入理解

    转自:http://www.cnblogs.com/vivounicorn/archive/2011/09/23/2186483.html Mahout学习——Canopy Clustering 聚类 ...

  7. PRML读书会第七章 Sparse Kernel Machines(支持向量机, support vector machine ,KKT条件,RVM)

    主讲人 网神 (新浪微博: @豆角茄子麻酱凉面) 网神(66707180) 18:59:22  大家好,今天一起交流下PRML第7章.第六章核函数里提到,有一类机器学习算法,不是对参数做点估计或求其分 ...

  8. [zz] 混合高斯模型 Gaussian Mixture Model

    聚类(1)——混合高斯模型 Gaussian Mixture Model http://blog.csdn.net/jwh_bupt/article/details/7663885 聚类系列: 聚类( ...

  9. 从决策树学习谈到贝叶斯分类算法、EM、HMM --别人的,拷来看看

    从决策树学习谈到贝叶斯分类算法.EM.HMM     引言 最近在面试中,除了基础 &  算法 & 项目之外,经常被问到或被要求介绍和描述下自己所知道的几种分类或聚类算法(当然,这完全 ...

随机推荐

  1. JS中如何获取<Select>中value和text的值

    原文地址:JS中如何获取<Select>中value和text的值 html代码: <select id = "city" onchange="chan ...

  2. 【Flask】filter 常用查询条件

    1. equal    2. not equal  3. like, ilike  4.in  5. not in 6. is null  7. is not null  8. and  9. or ...

  3. wget 监控web服务器

    wget --timeout=$timeout --tries=$times $url -q &>/dev/null --timeout=number 设定超时时间 --tries=nu ...

  4. Luogu-2657 [SCOI2009]windy数

    很少做数位\(dp\)的题,做道题学习一下吧. 记忆化搜索,\(f[10][10][2][2]\)分别记录当前位置,上一位数,是否有前导零和是否有大小上限. 题目要满足相邻两个数相差不小于2,如果有前 ...

  5. unigui中TUniDBEdit的OnEndDrag问题

    非常奇怪,unigui中TUniDBEdit未发布OnEndDrag属性,包括其子类:TUniDBNumberEdit.TUniDBFormattedNumberEdit.而其他数据感知组件都有OnE ...

  6. matlab之sub2ind()函数

    将下标转换为线性索引 语法 linearInd = sub2ind(matrixSize, rowSub, colSub) 说明 linearInd = sub2ind(matrixSize, row ...

  7. 英语发音规则---th

    英语发音规则---th 一.总结 一句话总结: th发/ð/音的情况:a.在th后以字母-er结尾的单词中:b.在代词.冠词.介词.连词或副词中的字母组合th th发/θ/音的情况:a.在数词(包括基 ...

  8. 初识JQuery(1)-选择器

    初识jquery 在学习jquery之前,就有看过一些相关的视频,才知道它是可以写很少的代码就可以完成很多事的.记得第一写轮播图的时候,首先就百度了篇轮播图的实现,当时还不知道自己百度的其实不是原生的 ...

  9. jQuery购物数量数字加减运算效果

    <a href="###" id="add" value="+">+</a> <input type=&quo ...

  10. [冬令营模拟]GTSG2018

    上学期没有去 GTSG,于是今天老师让我们来做一下 GTSG2018 Day1 & Day3 Day1 在上午当成一场考试来搞了,Day3 由于锅太多而且 T3 玄学而被放到下午自学... 上 ...