mahout LDA

什麼是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、下載數據

2、解壓縮數據

解壓縮之後,reuters-sgm下,包含了若干*.sgm文件,每個文件中又包含了若干下屬結構化文檔:

在下文中,我們主要使用<TITLE>和<BODY>中的文本。即標題+正文。

3、抽取

Mahout中內置了對上述Reuters預料的抽取程序,我們可以直接使用。

如上所述,抽取好的結果在./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的轉換過程。

上述命令蘊含了2個大坑,在其他文檔中均沒有仔細說明:
(1) -xm sequential,表示在本地執行,而不是用MapReduce執行。如果是後者,我們勢必要將這些小文件上傳到HDFS上,那樣的話,還要SequenceFile做甚……
(2) 然而seqdirectory在執行的時候,並不因爲十本地模式,就在本地文件系統上尋找。而是根據-i -o的文件系統前綴來判斷文件位置。也就是說,默認情況,依然十在HDFS上查找的……所以,這個file://的前綴是非常有必要的。

其他2個參數:

  • -c UTF8:編碼。
  • -chunk 64:64MB一個Chunk,應該和HDFS的BLOCK保持一致或者倍數關係。

5、轉換爲向量表示

爲了適應多種數據,聚類算法多使用向量空間作爲輸入數據。

首先在HDFS  上創建目錄user/coder4

hadoop  fs  -mkdir  user/coder4

由於我們先前已經得到了處理好的SequenceFile,從這一步開始,就可以在Hadoop上進行啦。

開始text->Vector的轉換:

輸入和輸出不解釋了。在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變換,默認不變換。

看一下產出:

說明各個文件的用途:

  • 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

參數說明如下:

  • -i:輸入爲上面產出的tfidf向量。
  • -o:每一輪迭代的結果將輸出在這裏。
  • -k:幾個簇。
  • -c:這是一個神奇的變量。若不設定k,則用這個目錄裏面的點,作爲聚類中心點。否則,隨機選擇k個點,作爲中心點。
  • -dm:距離公式,文本類型推薦用cosine距離。
  • -x :最大迭代次數。
  • –clustering:在mapreduce模式運行。
  • –convergenceDelta:迭代收斂閾值,默認0.5,對於Cosine來說略大。

輸出1,初始隨機選擇的中心點:

輸出2,聚類過程、結果:

其中,clusters-k(-final)爲每次迭代後,簇的20箇中心點的信息。

而clusterdPoints,存儲了 簇id -> 文檔id 的映射。

2、查看簇結果

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

要說明的是,clusterdump似乎只能在本地執行……所以先把數據下載到本地吧。

參數說明:

  • -i :我們只看最終迭代生成的簇結果。
  • -d :使用 詞 -> 詞id 映射,使得我們輸出結果中,可以直接顯示每個簇,權重最高的詞文本,而不是詞id。
  • -dt:上面映射類型,由於我們是seqdictionary生成的,so。。
  • -o:最終產出目錄
  • -n:每個簇,只輸出20個權重最高的詞。

看看dump結果吧:

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

其中前面的12722是簇的ID,n=1305即簇中有這麼多個文檔。c向量是簇中心點向量,格式爲 詞文本:權重(點座標),r是簇的半徑向量,格式爲 詞文本:半徑。

下面的Top Terms是簇中選取出來的特徵詞。

3、查看聚類結果

其實,聚類結果中,更重要的是,文檔被聚到了哪個類。

遺憾的是,在很多資料中,都沒有說明這一點。前文我們已經提到了,簇id -> 文檔id的結果,保存在了clusteredPoints下面。這也是mahout內置類型存儲的。我們可以用seqdumper命令查看。

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

如果不指定-o,默認輸出到屏幕,輸出結果爲形如:

其實,這個輸出是一個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

Clustering - Fuzzy C-means

2、工具用法

執行Fuzzy-KMeans

新增算法的柔軟參數m,若m接近於1則接近於KMeans;隨着m增加,會有越來越多的聚簇重疊(越多的點同時屬於多個聚簇)。

3、查看隸屬度

如上文所述,在Fuzzy-KMeans中,點以一定的 “概率” 隸屬於聚簇。

我們可以用seqdumper查看隸屬度:

其中的 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聚類

如上所述,在距離的計算方面,我們選擇了歐式距離。閾值T1=150, t2=75。

輸出結果,也可以用ClusterDump查看。

這是一個粗略、大致的結果。在實際應用中,經常被用來作爲K-Means的初始聚簇中心,來代替隨機選擇的K箇中心點。這一做法有2個優點:
(1) 無需決定K,因爲我們的預設往往是不準的。
(2) 使用Canopy的聚類結果,是一個大致準確的中心點。而隨機選擇很可能陷入局部最優。

在執行k-means時,若我們不指定k,則會使用-c的路徑作爲初始聚簇中心點,並跳過隨機選擇的過程。

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等聚類中,需要將每個樣本轉化爲一個向量。
  • 譜聚類中,則需要直接給一個矩陣,其中存儲了任意兩個樣本之間的相似度。

例如:

2013081119022654

在實際應用中,相似矩陣(affinity matrix)是相當稀疏的。所以,Mahout採用了鄰接矩陣的輸入格式,即(i, j, affinity)表示第i個樣本與第j個樣本的相似度是affinity。

同時,還需要輸入矩陣的維度。原因應該是很好理解的。

如上圖中的數據,轉化完畢後,就是:

Mahout中,將譜聚類與KMeans進行了整合,執行命令:

參數說明:

  • -i :輸入的相似度矩陣,鄰接矩陣。
  • -k:目標聚成2個簇。
  • -o:聚簇中間結果。
  • -d:相似度矩陣維度爲6,也即樣本共6個。
  • -x:100,最多迭代100次。
  • -cd:收斂閾值,默認0.5

其他可選參數:

  • -ssvd:使用svd矩陣分解降維。
  • -q:svd相關。

輸出的目錄結構,與K-Means等相似:

說明一下:

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

先看一下聚簇映射:

如上所示,這個順序,是按照輸入樣本順序來的。Key 1表示屬於第2個簇,0表示第1個簇。distance是點與簇的相似距離。

然後來看一下簇中心:

輸出結果:

於 K-Means一樣,VL-XX是簇名稱,n代表簇中含有幾個元素。c是簇中心,r是簇半徑。

然而奇怪的是,我們可以發現,上面的n都是錯的,而下面簇中點的打印是對的不知道是什麼Bug…

六、LDA

LDA是一種主題模型,它是一種考慮了詞貢獻的,較爲高級的“聚類”算法,主要功能爲:

  1. 給定主題數k,輸出文檔屬於每個主題的概率(越大表示越貼近該主題)。
  2. 輸出每個主題中,權重最大的幾個詞。相當於傳統聚類之後的Tag。

關於算法、原理方面,本文就不做過多的介紹了,感興趣的可以查看相關論文。

考慮到LDA的特性,提取特徵的時候,我們需要使用tf而非tfidf:

Mahout實現的LDA有個大坑:tf的vector,詞必須是Ingeter類型,即要我們把word轉換成wordid。

生成的有2個子目錄,我們只用下面這個matrix:

LDA訓練:

上述參數,說明一下:

  • -k 主題數20
  • -dt:輸出的?
  • -o:輸出的?
  • -x:迭代100次,其實對於LDA,1000~2000次是比較合理的。
  • -nt:詞的數量,即dictionary.file-0的大小。

PS:Mahout這個LDA,執行效率真心不高,也可能是我的數據太小,機器太少。

文檔->主題的概率

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

  • Key是文檔id,與文件的對應關係可以在/user/coder4/reuters-cvb-vectoers/docIndex中查看。
  • Value是文檔屬於Topic 0~19的概率。按照值Sort一下,就能知道文檔屬於哪個主題的概率最大。

主題->詞的概率

  • 一共有20行有效輸出,Key 0~19,代表了20個主題。
  • 每個Value中有41806個詞的權重。表示了詞屬於當前主題的權重。

本來有個LDAPrintTopics,可以直接打印Topic對應的詞的,但是年久失修,已經不能用在新版的cvb的LDA上了。大家可以寫程序對上免每個Topic中詞的權重進行排序,從而獲得每個主題的代表詞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章