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、下载数据
| axel -n 20 http://kdd.ics.uci.edu/databases/reuters21578/reuters21578.tar.gz |
2、解压缩数据
|
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-<SRD>-TO 02-26 0082</UNKNOWN> <TEXT> <TITLE>STANDARD OIL <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 <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预料的抽取程序,我们可以直接使用。
|
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的转换过程。
| 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上进行啦。
|
hadoop
dfs
-put
reuters-seq
/user/coder4
|
开始text->Vector的转换:
| 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变换,默认不变换。
看一下产出:
|
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
| 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,初始随机选择的中心点:
|
hadoop
dfs
-ls
/user/coder4/reuters-kmeans-clusters
Found
1
items
/user/coder4/reuters-kmeans-clusters/part-randomSeed
|
输出2,聚类过程、结果:
| 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)个簇的信息。
|
# 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命令查看。
|
mahout
seqdumper
-i
/user/coder4/reuters-kmeans/clusteredPoints/
|
其中,-d和-dt的原因同clusterdump。
如果不指定-o,默认输出到屏幕,输出结果为形如:
| 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
|
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查看隶属度:
| 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聚类
|
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查看。
| 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的路径作为初始聚簇中心点,并跳过随机选择的过程。
|
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过小,会增加簇的个数,以及计算时间。
网上有人给出了这个做法,仅供参考:
-
对数据进行采样。
-
计算所有文档之间的平均距离(使用要在Canopy中用的距离公式)。
-
T1 = 平均距离 * 2;T2 = 平均距离。
上述做法有一定道理,但我认为,以下更加合理:
-
对数据进行采样。
-
选择一个T2,T1 = 2 * T1。
-
进行聚类,并评测聚类效果,可使用k-fold交叉验证。
-
迭代选择下一个T2。
-
直到找到最优的T1 T2。
五、Spectral
1、谱聚类算法简介
谱聚类算法,参考了文章《Mahout Spectral聚类》。
谱聚类算法是一种较为现代的图聚类算法。与K-Means等传统聚类相比,它具有以下特点:
-
可以对非欧式距离空间的点进行聚类。传统K-Means将点视为向量,并计算距离。而谱聚类算法要求直接给出两样本间相似度的矩阵。使得一些不便于在欧式空间计算的多特征聚类问题,有了更好的解法。(例如,性别,年龄2个特征,在欧式空间中就没有显著意义)。
-
上面的这一更宽泛的约束条件,使得谱聚类对样本空间的形状无限制,并能收敛于全局最优解(无需使用)。
一种典型的谱聚类算法的大致流程是:
-
构建样本集的相似度矩阵W。
-
对相似度矩阵W进行稀疏化,形成新的相似度矩阵A。
-
构建相似度矩阵A的拉普拉斯矩阵L。
-
计算拉普拉斯矩阵L的前k个特征值与特征向量,构建特征向量空间。
-
将前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进行了整合,执行命令:
|
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等相似:
| /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:最终聚簇的信息。
先看一下聚簇映射:
|
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是点与簇的相似距离。
然后来看一下簇中心:
| mahout clusterdump -i /user/coder4/sc-spectral/kmeans_out/clusters-1-final |
输出结果:
|
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是一种主题模型,它是一种考虑了词贡献的,较为高级的“聚类”算法,主要功能为:
-
给定主题数k,输出文档属于每个主题的概率(越大表示越贴近该主题)。
-
输出每个主题中,权重最大的几个词。相当于传统聚类之后的Tag。
关于算法、原理方面,本文就不做过多的介绍了,感兴趣的可以查看相关论文。
考虑到LDA的特性,提取特征的时候,我们需要使用tf而非tfidf:
| 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。
|
mahout
rowid
-i
/user/coder4/reuters-sparse/tf-vectors
-o
/user/coder4/reuters-cvb-vectoers
|
生成的有2个子目录,我们只用下面这个matrix:
| hadoop dfs -ls /user/coder4/reuters-cvb-vectoers Found 2 items /user/coder4/reuters-cvb-vectoers/docIndex /user/coder4/reuters-cvb-vectoers/matrix |
LDA训练:
|
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,执行效率真心不高,也可能是我的数据太小,机器太少。
文档->主题的概率
| mahout seqdumper -i /user/coder4/reuters-lda-documents |
输出共21578行,代表了文档集合中的所有文档。
- Key是文档id,与文件的对应关系可以在/user/coder4/reuters-cvb-vectoers/docIndex中查看。
- Value是文档属于Topic 0~19的概率。按照值Sort一下,就能知道文档属于哪个主题的概率最大。
|
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}
|
主题->词的概率
| 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、下载数据
|
axel
-n
20
http://kdd.ics.uci.edu/databases/reuters21578/reuters21578.tar.gz
|
2、解压缩数据
| 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-<SRD>-TO
02-26
0082</UNKNOWN>
<TEXT>
<TITLE>STANDARD
OIL
<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
<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预料的抽取程序,我们可以直接使用。
| 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的转换过程。
|
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上进行啦。
| hadoop dfs -put reuters-seq /user/coder4 |
开始text->Vector的转换:
|
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变换,默认不变换。
看一下产出:
| 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
|
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,初始随机选择的中心点:
| hadoop dfs -ls /user/coder4/reuters-kmeans-clusters Found 1 items /user/coder4/reuters-kmeans-clusters/part-randomSeed |
输出2,聚类过程、结果:
|
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)个簇的信息。
| # 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命令查看。
| mahout seqdumper -i /user/coder4/reuters-kmeans/clusteredPoints/ |
其中,-d和-dt的原因同clusterdump。
如果不指定-o,默认输出到屏幕,输出结果为形如:
|
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
| 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查看隶属度:
|
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聚类
| 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查看。
|
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的路径作为初始聚簇中心点,并跳过随机选择的过程。
| 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过小,会增加簇的个数,以及计算时间。
网上有人给出了这个做法,仅供参考:
- 对数据进行采样。
- 计算所有文档之间的平均距离(使用要在Canopy中用的距离公式)。
- T1 = 平均距离 * 2;T2 = 平均距离。
上述做法有一定道理,但我认为,以下更加合理:
- 对数据进行采样。
- 选择一个T2,T1 = 2 * T1。
- 进行聚类,并评测聚类效果,可使用k-fold交叉验证。
- 迭代选择下一个T2。
- 直到找到最优的T1 T2。
五、Spectral
1、谱聚类算法简介
谱聚类算法,参考了文章《Mahout Spectral聚类》。
谱聚类算法是一种较为现代的图聚类算法。与K-Means等传统聚类相比,它具有以下特点:
- 可以对非欧式距离空间的点进行聚类。传统K-Means将点视为向量,并计算距离。而谱聚类算法要求直接给出两样本间相似度的矩阵。使得一些不便于在欧式空间计算的多特征聚类问题,有了更好的解法。(例如,性别,年龄2个特征,在欧式空间中就没有显著意义)。
- 上面的这一更宽泛的约束条件,使得谱聚类对样本空间的形状无限制,并能收敛于全局最优解(无需使用)。
一种典型的谱聚类算法的大致流程是:
- 构建样本集的相似度矩阵W。
- 对相似度矩阵W进行稀疏化,形成新的相似度矩阵A。
- 构建相似度矩阵A的拉普拉斯矩阵L。
- 计算拉普拉斯矩阵L的前k个特征值与特征向量,构建特征向量空间。
- 将前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进行了整合,执行命令:
| 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等相似:
|
/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:最终聚簇的信息。
先看一下聚簇映射:
| 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是点与簇的相似距离。
然后来看一下簇中心:
|
mahout
clusterdump
-i
/user/coder4/sc-spectral/kmeans_out/clusters-1-final
|
输出结果:
| 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是一种主题模型,它是一种考虑了词贡献的,较为高级的“聚类”算法,主要功能为:
- 给定主题数k,输出文档属于每个主题的概率(越大表示越贴近该主题)。
- 输出每个主题中,权重最大的几个词。相当于传统聚类之后的Tag。
关于算法、原理方面,本文就不做过多的介绍了,感兴趣的可以查看相关论文。
考虑到LDA的特性,提取特征的时候,我们需要使用tf而非tfidf:
|
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。
| mahout rowid -i /user/coder4/reuters-sparse/tf-vectors -o /user/coder4/reuters-cvb-vectoers |
生成的有2个子目录,我们只用下面这个matrix:
|
hadoop
dfs
-ls
/user/coder4/reuters-cvb-vectoers
Found
2
items
/user/coder4/reuters-cvb-vectoers/docIndex
/user/coder4/reuters-cvb-vectoers/matrix
|
LDA训练:
| 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,执行效率真心不高,也可能是我的数据太小,机器太少。
文档->主题的概率
|
mahout
seqdumper
-i
/user/coder4/reuters-lda-documents
|
输出共21578行,代表了文档集合中的所有文档。
-
Key是文档id,与文件的对应关系可以在/user/coder4/reuters-cvb-vectoers/docIndex中查看。
-
Value是文档属于Topic 0~19的概率。按照值Sort一下,就能知道文档属于哪个主题的概率最大。
| 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} |
主题->词的概率
|
mahout
seqdumper
-i
/user/coder4/reuters-lda
|
-
一共有20行有效输出,Key 0~19,代表了20个主题。
-
每个Value中有41806个词的权重。表示了词属于当前主题的权重。
本来有个LDAPrintTopics,可以直接打印Topic对应的词的,但是年久失修,已经不能用在新版的cvb的LDA上了。大家可以写程序对上免每个Topic中词的权重进行排序,从而获得每个主题的代表词。