天天看點

探索推薦引擎内部的秘密,第 3 部分: 深入推薦引擎相關算法 - 聚類

探索推薦引擎内部的秘密,第 3 部分: 深入推薦引擎相關算法 - 聚類

在 IBM Bluemix 雲平台上開發并部署您的下一個應用。

開始您的試用

http://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy1/index.html?ca=drs-

http://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy2/index.html?ca=drs-

http://www.ibm.com/developerworks/cn/web/1103_zhaoct_recommstudy3/index.html?ca=drs-

感覺寫的很好,儲存下來。

智能推薦大都基于海量資料的計算和處理,然而我們發現在海量資料上高效的運作協同過濾算法以及其他推薦政策這樣高複雜的算法是有很大的挑戰的,在面對解決這個問題的過程中,大家提出了很多減少計算量的方法,而聚類無疑是其中最優的選擇之一。 聚類 (Clustering) 是一個資料挖掘的經典問題,它的目的是将資料分為多個簇 (Cluster),在同一個簇中的對象之間有較高的相似度,而不同簇的對象差别較大。聚類被廣泛的應用于資料處理和統計分析領域。Apache Mahout 是 ASF(Apache Software Foundation) 的一個較新的開源項目,它源于 Lucene,建構在 Hadoop 之上,關注海量資料上的機器學習經典算法的高效實作。本文主要介紹如何基于 Apache Mahout 實作高效的聚類算法,進而實作更高效的資料處理和分析的應用。

聚類分析

什麼是聚類分析?

聚類 (Clustering) 就是将資料對象分組成為多個類或者簇 (Cluster),它的目标是:在同一個簇中的對象之間具有較高的相似度,而不同簇中的對象差别較大。是以,在很多應用中,一個簇中的資料對象可以被作為一個整體來對待,進而減少計算量或者提高計算品質。

其實聚類是一個人們日常生活的常見行為,即所謂“物以類聚,人以群分”,核心的思想也就是聚類。人們總是不斷地改進下意識中的聚類模式來學習如何區分各個事物和人。同時,聚類分析已經廣泛的應用在許多應用中,包括模式識别,資料分析,圖像處理以及市場研究。通過聚類,人們能意識到密集和稀疏的區域,發現全局的分布模式,以及資料屬性之間的有趣的互相關系。

聚類同時也在 Web 應用中起到越來越重要的作用。最被廣泛使用的既是對 Web 上的文檔進行分類,組織資訊的釋出,給使用者一個有效分類的内容浏覽系統(門戶網站),同時可以加入時間因素,進而發現各個類内容的資訊發展,最近被大家關注的主題和話題,或者分析一段時間内人們對什麼樣的内容比較感興趣,這些有趣的應用都得建立在聚類的基礎之上。作為一個資料挖掘的功能,聚類分析能作為獨立的工具來獲得資料分布的情況,觀察每個簇的特點,集中對特定的某些簇做進一步的分析,此外,聚類分析還可以作為其他算法的預處理步驟,簡化計算量,提高分析效率,這也是我們在這裡介紹聚類分析的目的。

不同的聚類問題

對于一個聚類問題,要挑選最适合最高效的算法必須對要解決的聚類問題本身進行剖析,下面我們就從幾個側面分析一下聚類問題的需求。

聚類結果是排他的還是可重疊的

為了很好了解這個問題,我們以一個例子進行分析,假設你的聚類問題需要得到二個簇:“喜歡詹姆斯卡梅隆電影的使用者”和“不喜歡詹姆斯卡梅隆的使用者”,這其實是一個排他的聚類問題,對于一個使用者,他要麼屬于“喜歡”的簇,要麼屬于不喜歡的簇。但如果你的聚類問題是“喜歡詹姆斯卡梅隆電影的使用者”和“喜歡裡奧納多電影的使用者”,那麼這個聚類問題就是一個可重疊的問題,一個使用者他可以既喜歡詹姆斯卡梅隆又喜歡裡奧納多。

是以這個問題的核心是,對于一個元素,他是否可以屬于聚類結果中的多個簇中,如果是,則是一個可重疊的聚類問題,如果否,那麼是一個排他的聚類問題。

基于層次還是基于劃分

其實大部分人想到的聚類問題都是“劃分”問題,就是拿到一組對象,按照一定的原則将它們分成不同的組,這是典型的劃分聚類問題。但除了基于劃分的聚類,還有一種在日常生活中也很常見的類型,就是基于層次的聚類問題,它的聚類結果是将這些對象分等級,在頂層将對象進行大緻的分組,随後每一組再被進一步的細分,也許所有路徑最終都要到達一個單獨執行個體,這是一種“自頂向下”的層次聚類解決方法,對應的,也有“自底向上”的。其實可以簡單的了解,“自頂向下”就是一步步的細化分組,而“自底向上”就是一步步的歸并分組。

簇數目固定的還是無限制的聚類

這個屬性很好了解,就是你的聚類問題是在執行聚類算法前已經确定聚類的結果應該得到多少簇,還是根據資料本身的特征,由聚類算法選擇合适的簇的數目。

基于距離還是基于機率分布模型

在本系列的第二篇介紹協同過濾的文章中,我們已經詳細介紹了相似性和距離的概念。基于距離的聚類問題應該很好了解,就是将距離近的相似的對象聚在一起。相比起來,基于機率分布模型的,可能不太好了解,那麼下面給個簡單的例子。

一個機率分布模型可以了解是在 N 維空間的一組點的分布,而它們的分布往往符合一定的特征,比如組成一個特定的形狀。基于機率分布模型的聚類問題,就是在一組對象中,找到能符合特定分布模型的點的集合,他們不一定是距離最近的或者最相似的,而是能完美的呈現出機率分布模型所描述的模型。

下面圖 1 給出了一個例子,對同樣一組點集,應用不同的聚類政策,得到完全不同的聚類結果。左側給出的結果是基于距離的,核心的原則就是将距離近的點聚在一起,右側給出的基于機率分布模型的聚類結果,這裡采用的機率分布模型是一定弧度的橢圓。圖中專門标出了兩個紅色的點,這兩點的距離很近,在基于距離的聚類中,将他們聚在一個類中,但基于機率分布模型的聚類則将它們分在不同的類中,隻是為了滿足特定的機率分布模型(當然這裡我特意舉了一個比較極端的例子)。是以我們可以看出,在基于機率分布模型的聚類方法裡,核心是模型的定義,不同的模型可能導緻完全不同的聚類結果。

圖 1 基于距離和基于機率分布模型的聚類問題
探索推薦引擎内部的秘密,第 3 部分: 深入推薦引擎相關算法 - 聚類

回頁首

Apache Mahout 中的聚類分析架構

Apache Mahout 是 Apache Software Foundation (ASF) 旗下的一個開源項目,提供一些可擴充的機器學習領域經典算法的實作,旨在幫助開發人員更加友善快捷地建立智能應用程式,并且,在 Mahout 的最近版本中還加入了對 Apache Hadoop 的支援,使這些算法可以更高效的運作在雲計算環境中。

關于 Apache Mahout 的安裝和配置請參考《基于 Apache Mahout 建構社會化推薦引擎》,它是筆者 09 年發表的一篇關于基于 Mahout 實作推薦引擎的 developerWorks 文章,其中詳細介紹了 Mahout 的安裝步驟。

Mahout 中提供了常用的多種聚類算法,涉及我們剛剛讨論過的各種類型算法的具體實作,下面我們就進一步深入幾個典型的聚類算法的原理,優缺點和實用場景,以及如何使用 Mahout 高效的實作它們。

回頁首

深入聚類算法

深入介紹聚類算法之前,這裡先對 Mahout 中對各種聚類問題的資料模型進行簡要的介紹。

資料模型

Mahout 的聚類算法将對象表示成一種簡單的資料模型:向量 (Vector)。在向量資料描述的基礎上,我們可以輕松的計算兩個對象的相似性,關于向量和向量的相似度計算,本系列的上一篇介紹協同過濾算法的文章中已經進行了詳細的介紹,請參考《“探索推薦引擎内部的秘密”系列 - Part 2: 深入推薦引擎相關算法 -- 協同過濾》。

Mahout 中的向量 Vector 是一個每個域是浮點數 (double) 的複合對象,最容易聯想到的實作就是一個浮點數的數組。但在具體應用由于向量本身資料内容的不同,比如有些向量的值很密集,每個域都有值;有些呢則是很稀疏,可能隻有少量域有值,是以 Mahout 提供了多個實作:

  1. DenseVector,它的實作就是一個浮點數數組,對向量裡所有域都進行存儲,适合用于存儲密集向量。
  2. RandomAccessSparseVector 基于浮點數的 HashMap 實作的,key 是整形 (int) 類型,value 是浮點數 (double) 類型,它隻存儲向量中不為空的值,并提供随機通路。
  3. SequentialAccessVector 實作為整形 (int) 類型和浮點數 (double) 類型的并行數組,它也隻存儲向量中不為空的值,但隻提供順序通路。

使用者可以根據自己算法的需求選擇合适的向量實作類,如果算法需要很多随機通路,應該選擇 DenseVector 或者 RandomAccessSparseVector,如果大部分都是順序通路,SequentialAccessVector 的效果應該更好。

介紹了向量的實作,下面我們看看如何将現有的資料模組化成向量,術語就是“如何對資料進行向量化”,以便采用 Mahout 的各種高效的聚類算法。

  1. 簡單的整形或浮點型的資料

    這種資料最簡單,隻要将不同的域存在向量中即可,比如 n 維空間的點,其實本身可以被描述為一個向量。

  2. 枚舉類型資料

    這類資料是對物體的描述,隻是取值範圍有限。舉個例子,假設你有一個蘋果資訊的資料集,每個蘋果的資料包括:大小,重量,顔色等,我們以顔色為例,設蘋果的顔色資料包括:紅色,黃色和綠色。在對資料進行模組化時,我們可以用數字來表示顔色,紅色 =1,黃色 =2,綠色 =3,那麼大小直徑 8cm,重量 0.15kg,顔色是紅色的蘋果,模組化的向量就是 <8, 0.15, 1>。

    下面的清單 1 給出了對以上兩種資料進行向量化的例子。

    清單 1. 建立簡單的向量
    // 建立一個二維點集的向量組
     public static final double[][] points = { { 1, 1 }, { 2, 1 }, { 1, 2 }, 
     { 2, 2 }, { 3, 3 },  { 8, 8 }, { 9, 8 }, { 8, 9 }, { 9, 9 }, { 5, 5 }, 
     { 5, 6 }, { 6, 6 }}; 
     public static List<Vector> getPointVectors(double[][] raw) { 
    	 List<Vector> points = new ArrayList<Vector>(); 
    	 for (int i = 0; i < raw.length; i++) { 
    		 double[] fr = raw[i]; 
     // 這裡選擇建立 RandomAccessSparseVector 
    		 Vector vec = new RandomAccessSparseVector(fr.length); 
    		 // 将資料存放在建立的 Vector 中
     vec.assign(fr); 
    		 points.add(vec); 
    	 } 
    	 return points; 
     } 
    
     // 建立蘋果資訊資料的向量組
     public static List<Vector> generateAppleData() { 
     List<Vector> apples = new ArrayList<Vector>(); 
     // 這裡建立的是 NamedVector,其實就是在上面幾種 Vector 的基礎上,
     //為每個 Vector 提供一個可讀的名字
    	 NamedVector apple = new NamedVector(new DenseVector(
    	 new double[] {0.11, 510, 1}), 
    		"Small round green apple"); 
    	 apples.add(apple); 
     apple = new NamedVector(new DenseVector(new double[] {0.2, 650, 3}), 
    		"Large oval red apple"); 
    	 apples.add(apple); 
    	 apple = new NamedVector(new DenseVector(new double[] {0.09, 630, 1}), 
    		"Small elongated red apple"); 
    	 apples.add(apple); 
    	 apple = new NamedVector(new DenseVector(new double[] {0.25, 590, 3}), 
    		"Large round yellow apple"); 
    	 apples.add(apple); 
    	 apple = new NamedVector(new DenseVector(new double[] {0.18, 520, 2}), 
    		"Medium oval green apple"); 
    	 apples.add(apple); 
    	 return apples; 
     }      
  3. 文本資訊

    作為聚類算法的主要應用場景 - 文本分類,對文本資訊的模組化也是一個常見的問題。在資訊檢索研究領域已經有很好的模組化方式,就是資訊檢索領域中最常用的向量空間模型 (Vector Space Model, VSM)。因為向量空間模型不是本文的重點,這裡給一個簡要的介紹,有興趣的朋友可以查閱參考目錄中給出的相關文檔。

    文本的向量空間模型就是将文本資訊模組化為一個向量,其中每一個域是文本中出現的一個詞的權重。關于權重的計算則有很多中:

    • 最簡單的莫過于直接計數,就是詞在文本裡出現的次數。這種方法簡單,但是對文本内容描述的不夠精确。
    • 詞的頻率 (Team Frequency, TF):就是将詞在文本中出現的頻率作為詞的權重。這種方法隻是對于直接計數進行了歸一化處理,目的是讓不同長度的文本模型有統一的取值空間,便于文本相似度的比較,但可以看出,簡單計數和詞頻都不能解決“高頻無意義詞彙權重大的問題”,也就是說對于英文文本中,“a”,“the”這樣高頻但無實際意義的詞彙并沒有進行過濾,這樣的文本模型在計算文本相似度時會很不準确。
    • 詞頻 - 逆向文本頻率 (Term Frequency – Inverse Document Frequency, TF-IDF):它是對 TF 方法的一種加強,字詞的重要性随着它在檔案中出現的次數成正比增加,但同時會随着它在所有文本中出現的頻率成反比下降。舉個例子,對于“高頻無意義詞彙”,因為它們大部分會出現在所有的文本中,是以它們的權重會大打折扣,這樣就使得文本模型在描述文本特征上更加精确。在資訊檢索領域,TF-IDF 是對文本資訊模組化的最常用的方法。
    對于文本資訊的向量化,Mahout 已經提供了工具類,它基于 Lucene 給出了對文本資訊進行分析,然後建立文本向量。下面的清單 2 給出了一個例子,分析的文本資料是路透提供的新聞資料,參考資源裡給出了下載下傳位址。将資料集下載下傳後,放在“clustering/reuters”目錄下。
    清單 2. 建立文本資訊的向量
    public static void documentVectorize(String[] args) throws Exception{ 
    	 //1. 将路透的資料解壓縮 , Mahout 提供了專門的方法
     DocumentClustering.extractReuters(); 
     //2. 将資料存儲成 SequenceFile,因為這些工具類就是在 Hadoop 的基礎上做的,是以首先我們需要将資料寫
     //    成 SequenceFile,以便讀取和計算
    	 DocumentClustering.transformToSequenceFile(); 
     //3. 将 SequenceFile 檔案中的資料,基于 Lucene 的工具進行向量化
    	 DocumentClustering.transformToVector(); 	
     } 
    
     public static void extractReuters(){ 
     //ExtractReuters 是基于 Hadoop 的實作,是以需要将輸入輸出的檔案目錄傳給它,這裡我們可以直接把它映
     // 射到我們本地的一個檔案夾,解壓後的資料将寫入輸出目錄下
    	 File inputFolder = new File("clustering/reuters"); 
    	 File outputFolder = new File("clustering/reuters-extracted"); 
    	 ExtractReuters extractor = new ExtractReuters(inputFolder, outputFolder); 
     extractor.extract(); 
     } 
    	
     public static void transformToSequenceFile(){ 
     //SequenceFilesFromDirectory 實作将某個檔案目錄下的所有檔案寫入一個 SequenceFiles 的功能
     // 它其實本身是一個工具類,可以直接用指令行調用,這裡直接調用了它的 main 方法
    	 String[] args = {"-c", "UTF-8", "-i", "clustering/reuters-extracted/", "-o",
    	 "clustering/reuters-seqfiles"}; 
             // 解釋一下參數的意義:
     // 	 -c: 指定檔案的編碼形式,這裡用的是"UTF-8"
     // 	 -i: 指定輸入的檔案目錄,這裡指到我們剛剛導出檔案的目錄
     // 	 -o: 指定輸出的檔案目錄
    
    	 try { 
    		 SequenceFilesFromDirectory.main(args); 
    	 } catch (Exception e) { 
    		 e.printStackTrace(); 
    	 } 
     } 
    	
     public static void transformToVector(){ 
     //SparseVectorsFromSequenceFiles 實作将 SequenceFiles 中的資料進行向量化。
     // 它其實本身是一個工具類,可以直接用指令行調用,這裡直接調用了它的 main 方法
     String[] args = {"-i", "clustering/reuters-seqfiles/", "-o", 
     "clustering/reuters-vectors-bigram", "-a", 
     "org.apache.lucene.analysis.WhitespaceAnalyzer"
    , "-chunk", "200", "-wt", "tfidf", "-s", "5", 
    "-md", "3", "-x", "90", "-ng", "2", "-ml", "50", "-seq"}; 
     // 解釋一下參數的意義:
     // 	 -i: 指定輸入的檔案目錄,這裡指到我們剛剛生成 SequenceFiles 的目錄
     // 	 -o: 指定輸出的檔案目錄
     // 	 -a: 指定使用的 Analyzer,這裡用的是 lucene 的空格分詞的 Analyzer 
     // 	 -chunk: 指定 Chunk 的大小,機關是 M。對于大的檔案集合,我們不能一次 load 所有檔案,是以需要
     // 		對資料進行切塊
     // 	 -wt: 指定分析時采用的計算權重的模式,這裡選了 tfidf 
     // 	 -s:  指定詞語在整個文本集合出現的最低頻度,低于這個頻度的詞彙将被丢掉
     // 	 -md: 指定詞語在多少不同的文本中出現的最低值,低于這個值的詞彙将被丢掉
     // 	 -x:  指定高頻詞彙和無意義詞彙(例如 is,a,the 等)的出現頻率上限,高于上限的将被丢掉
     // 	 -ng: 指定分詞後考慮詞彙的最大長度,例如 1-gram 就是,coca,cola,這是兩個詞,
     // 	      2-gram 時,coca cola 是一個詞彙,2-gram 比 1-gram 在一定情況下分析的更準确。
     // 	 -ml: 指定判斷相鄰詞語是不是屬于一個詞彙的相似度門檻值,當選擇 >1-gram 時才有用,其實計算的是
     // 	      Minimum Log Likelihood Ratio 的門檻值
     // 	 -seq: 指定生成的向量是 SequentialAccessSparseVectors,沒設定時預設生成還是
     //       RandomAccessSparseVectors 
    
    	 try { 
    		 SparseVectorsFromSequenceFiles.main(args); 
    	 } catch (Exception e) { 
    		 e.printStackTrace(); 
    	 } 
     }      
    這裡補充一點,生成的向量化檔案的目錄結構是這樣的:
    圖 2 文本資訊向量化
    探索推薦引擎内部的秘密,第 3 部分: 深入推薦引擎相關算法 - 聚類
    • df-count 目錄:儲存着文本的頻率資訊
    • tf-vectors 目錄:儲存着以 TF 作為權值的文本向量
    • tfidf-vectors 目錄:儲存着以 TFIDF 作為權值的文本向量
    • tokenized-documents 目錄:儲存着分詞過後的文本資訊
    • wordcount 目錄:儲存着全局的詞彙出現的次數
    • dictionary.file-0 目錄:儲存着這些文本的詞彙表
    • frequcency-file-0 目錄 : 儲存着詞彙表對應的頻率資訊。

介紹完向量化問題,下面我們深入分析各個聚類算法,首先介紹的是最經典的 K 均值算法。

K 均值聚類算法

K 均值是典型的基于距離的排他的劃分方法:給定一個 n 個對象的資料集,它可以建構資料的 k 個劃分,每個劃分就是一個聚類,并且 k<=n,同時還需要滿足兩個要求:

  • 每個組至少包含一個對象
  • 每個對象必須屬于且僅屬于一個組。

K 均值的基本原理是這樣的,給定 k,即要建構的劃分的數目,

  1. 首先建立一個初始劃分,随機地選擇 k 個對象,每個對象初始地代表了一個簇中心。對于其他的對象,根據其與各個簇中心的距離,将它們賦給最近的簇。
  2. 然後采用一種疊代的重定位技術,嘗試通過對象在劃分間移動來改進劃分。所謂重定位技術,就是當有新的對象加入簇或者已有對象離開簇的時候,重新計算簇的平均值,然後對對象進行重新配置設定。這個過程不斷重複,直到沒有簇中對象的變化。

當結果簇是密集的,而且簇和簇之間的差別比較明顯時,K 均值的效果比較好。對于處理大資料集,這個算法是相對可伸縮的和高效的,它的複雜度是 O(nkt),n 是對象的個數,k 是簇的數目,t 是疊代的次數,通常 k<<n,且 t<<n,是以算法經常以局部最優結束。

K 均值的最大問題是要求使用者必須事先給出 k 的個數,k 的選擇一般都基于一些經驗值和多次實驗結果,對于不同的資料集,k 的取值沒有可借鑒性。另外,K 均值對“噪音”和孤立點資料是敏感的,少量這類的資料就能對平均值造成極大的影響。

說了這麼多理論的原理,下面我們基于 Mahout 實作一個簡單的 K 均值算法的例子。如前面介紹的,Mahout 提供了基本的基于記憶體的實作和基于 Hadoop 的 Map/Reduce 的實作,分别是 KMeansClusterer 和 KMeansDriver,下面給出一個簡單的例子,就基于我們在清單 1 裡定義的二維點集資料。

清單 3. K 均值聚類算法示例
// 基于記憶體的 K 均值聚類算法實作
 public static void kMeansClusterInMemoryKMeans(){ 
 // 指定需要聚類的個數,這裡選擇 2 類
 int k = 2; 
 // 指定 K 均值聚類算法的最大疊代次數
 int maxIter = 3; 
 // 指定 K 均值聚類算法的最大距離門檻值
 double distanceThreshold = 0.01; 
 // 聲明一個計算距離的方法,這裡選擇了歐幾裡德距離
 DistanceMeasure measure = new EuclideanDistanceMeasure(); 
 // 這裡建構向量集,使用的是清單 1 裡的二維點集
 List<Vector> pointVectors = SimpleDataSet.getPointVectors(SimpleDataSet.points); 
 // 從點集向量中随機的選擇 k 個作為簇的中心
 List<Vector> randomPoints = RandomSeedGenerator.chooseRandomPoints(pointVectors, k); 
 // 基于前面選中的中心建構簇
 List<Cluster> clusters = new ArrayList<Cluster>(); 
 int clusterId = 0; 
 for(Vector v : randomPoints){ 
	 clusters.add(new Cluster(v, clusterId ++, measure)); 
 } 
 // 調用 KMeansClusterer.clusterPoints 方法執行 K 均值聚類
 List<List<Cluster>> finalClusters = KMeansClusterer.clusterPoints(pointVectors, 
 clusters, measure, maxIter, distanceThreshold); 

 // 列印最終的聚類結果
 for(Cluster cluster : finalClusters.get(finalClusters.size() -1)){ 
	 System.out.println("Cluster id: " + cluster.getId() + 
" center: " + cluster.getCenter().asFormatString()); 
	 System.out.println("       Points: " + cluster.getNumPoints()); 	
 } 
 } 
 // 基于 Hadoop 的 K 均值聚類算法實作
 public static void kMeansClusterUsingMapReduce () throws Exception{ 
 // 聲明一個計算距離的方法,這裡選擇了歐幾裡德距離
	 DistanceMeasure measure = new EuclideanDistanceMeasure(); 
	 // 指定輸入路徑,如前面介紹的一樣,基于 Hadoop 的實作就是通過指定輸入輸出的檔案路徑來指定資料源的。
	 Path testpoints = new Path("testpoints"); 
	 Path output = new Path("output"); 
	 // 清空輸入輸出路徑下的資料
 HadoopUtil.overwriteOutput(testpoints); 
	 HadoopUtil.overwriteOutput(output); 
	 RandomUtils.useTestSeed(); 
 // 在輸入路徑下生成點集,與記憶體的方法不同,這裡需要把所有的向量寫進檔案,下面給出具體的例子
	 SimpleDataSet.writePointsToFile(testpoints); 
 // 指定需要聚類的個數,這裡選擇 2 類
 int k = 2; 
 // 指定 K 均值聚類算法的最大疊代次數
 int maxIter = 3; 
	 // 指定 K 均值聚類算法的最大距離門檻值
 double distanceThreshold = 0.01; 
 // 随機的選擇 k 個作為簇的中心
 Path clusters = RandomSeedGenerator.buildRandom(testpoints, 
 new Path(output, "clusters-0"), k, measure); 
 // 調用 KMeansDriver.runJob 方法執行 K 均值聚類算法
 KMeansDriver.runJob(testpoints, clusters, output, measure, 
 distanceThreshold, maxIter, 1, true, true); 
 // 調用 ClusterDumper 的 printClusters 方法将聚類結果列印出來。
 ClusterDumper clusterDumper = new ClusterDumper(new Path(output, 
"clusters-" + maxIter -1), new Path(output, "clusteredPoints")); 
 clusterDumper.printClusters(null); 
 } 
 //SimpleDataSet 的 writePointsToFile 方法,将測試點集寫入檔案裡
 // 首先我們将測試點集包裝成 VectorWritable 形式,進而将它們寫入檔案
 public static List<VectorWritable> getPoints(double[][] raw) { 
	 List<VectorWritable> points = new ArrayList<VectorWritable>(); 
 for (int i = 0; i < raw.length; i++) { 
		 double[] fr = raw[i]; 
		 Vector vec = new RandomAccessSparseVector(fr.length); 
		 vec.assign(fr); 
 // 隻是在加入點集前,在 RandomAccessSparseVector 外加了一層 VectorWritable 的包裝
		 points.add(new VectorWritable(vec)); 
	 } 
 return points; 
 } 
 // 将 VectorWritable 的點集寫入檔案,這裡涉及一些基本的 Hadoop 程式設計元素,詳細的請參閱參考資源裡相關的内容
 public static void writePointsToFile(Path output) throws IOException { 
	 // 調用前面的方法生成點集
	 List<VectorWritable> pointVectors = getPoints(points); 
	 // 設定 Hadoop 的基本配置
	 Configuration conf = new Configuration(); 
	 // 生成 Hadoop 檔案系統對象 FileSystem 
	 FileSystem fs = FileSystem.get(output.toUri(), conf); 
 // 生成一個 SequenceFile.Writer,它負責将 Vector 寫入檔案中
	 SequenceFile.Writer writer = new SequenceFile.Writer(fs, conf, output, 
	 Text.class,  VectorWritable.class); 
	 // 這裡将向量按照文本形式寫入檔案
	 try { 
 for (VectorWritable vw : pointVectors) { 
 writer.append(new Text(), vw); 
		 } 
	 } finally { 
		 writer.close(); 
	 }  
 } 

執行結果
 KMeans Clustering In Memory Result 
 Cluster id: 0 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector",
"vector":"{\"values\":{\"table\":[0,1,0],\"values\":[1.8,1.8,0.0],\"state\":[1,1,0],
\"freeEntries\":1,\"distinct\":2,\"lowWaterMark\":0,\"highWaterMark\":1,
\"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},\"size\":2,\"lengthSquared\":-1.0}"} 
       Points: 5 
 Cluster id: 1 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector",
 "vector":"{\"values\":{\"table\":[0,1,0],
 \"values\":[7.142857142857143,7.285714285714286,0.0],\"state\":[1,1,0],
 \"freeEntries\":1,\"distinct\":2,\"lowWaterMark\":0,\"highWaterMark\":1,
 \"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},\"size\":2,\"lengthSquared\":-1.0}"} 
       Points: 7 

 KMeans Clustering Using Map/Reduce Result 
	 Weight:  Point: 
	 1.0: [1.000, 1.000] 
	 1.0: [2.000, 1.000] 
	 1.0: [1.000, 2.000] 
	 1.0: [2.000, 2.000] 
	 1.0: [3.000, 3.000] 
	 Weight:  Point: 
	 1.0: [8.000, 8.000] 
	 1.0: [9.000, 8.000] 
	 1.0: [8.000, 9.000] 
	 1.0: [9.000, 9.000] 
	 1.0: [5.000, 5.000] 
	 1.0: [5.000, 6.000] 
	 1.0: [6.000, 6.000]      

介紹完 K 均值聚類算法,我們可以看出它最大的優點是:原理簡單,實作起來也相對簡單,同時執行效率和對于大資料量的可伸縮性還是較強的。然而缺點也是很明确的,首先它需要使用者在執行聚類之前就有明确的聚類個數的設定,這一點是使用者在處理大部分問題時都不太可能事先知道的,一般需要通過多次試驗找出一個最優的 K 值;其次就是,由于算法在最開始采用随機選擇初始聚類中心的方法,是以算法對噪音和孤立點的容忍能力較差。所謂噪音就是待聚類對象中錯誤的資料,而孤立點是指與其他資料距離較遠,相似性較低的資料。對于 K 均值算法,一旦孤立點和噪音在最開始被選作簇中心,對後面整個聚類過程将帶來很大的問題,那麼我們有什麼方法可以先快速找出應該選擇多少個簇,同時找到簇的中心,這樣可以大大優化 K 均值聚類算法的效率,下面我們就介紹另一個聚類方法:Canopy 聚類算法。

Canopy 聚類算法

Canopy 聚類算法的基本原則是:首先應用成本低的近似的距離計算方法高效的将資料分為多個組,這裡稱為一個 Canopy,我們姑且将它翻譯為“華蓋”,Canopy 之間可以有重疊的部分;然後采用嚴格的距離計算方式準确的計算在同一 Canopy 中的點,将他們配置設定與最合适的簇中。Canopy 聚類算法經常用于 K 均值聚類算法的預處理,用來找合适的 k 值和簇中心。

下面詳細介紹一下建立 Canopy 的過程:初始,假設我們有一組點集 S,并且預設了兩個距離門檻值,T1,T2(T1>T2);然後選擇一個點,計算它與 S 中其他點的距離(這裡采用成本很低的計算方法),将距離在 T1 以内的放入一個 Canopy 中,同時從 S 中去掉那些與此點距離在 T2 以内的點(這裡是為了保證和中心距離在 T2 以内的點不能再作為其他 Canopy 的中心),重複整個過程直到 S 為空為止。

對 K 均值的實作一樣,Mahout 也提供了兩個 Canopy 聚類的實作,下面我們就看看具體的代碼例子。

清單 4. Canopy 聚類算法示例
//Canopy 聚類算法的記憶體實作
 public static void canopyClusterInMemory () { 
	 // 設定距離門檻值 T1,T2 
 double T1 = 4.0; 
	 double T2 = 3.0; 
 // 調用 CanopyClusterer.createCanopies 方法建立 Canopy,參數分别是:
	 // 	 1. 需要聚類的點集
	 // 	 2. 距離計算方法
	 // 	 3. 距離門檻值 T1 和 T2 
	 List<Canopy> canopies = CanopyClusterer.createCanopies( 
 SimpleDataSet.getPointVectors(SimpleDataSet.points), 
		 new EuclideanDistanceMeasure(), T1, T2); 
	 // 列印建立的 Canopy,因為聚類問題很簡單,是以這裡沒有進行下一步精确的聚類。
	 // 有必須的時候,可以拿到 Canopy 聚類的結果作為 K 均值聚類的輸入,能更精确更高效的解決聚類問題
 for(Canopy canopy : canopies) { 
		 System.out.println("Cluster id: " + canopy.getId() + 
" center: " + canopy.getCenter().asFormatString()); 
		 System.out.println("       Points: " + canopy.getNumPoints()); 	
	 } 
 } 

 //Canopy 聚類算法的 Hadoop 實作
 public static void canopyClusterUsingMapReduce() throws Exception{ 
	 // 設定距離門檻值 T1,T2 
 double T1 = 4.0; 
	 double T2 = 3.0; 
	 // 聲明距離計算的方法
	 DistanceMeasure measure = new EuclideanDistanceMeasure(); 
	 // 設定輸入輸出的檔案路徑
	 Path testpoints = new Path("testpoints"); 
	 Path output = new Path("output"); 
	 // 清空輸入輸出路徑下的資料
	 HadoopUtil.overwriteOutput(testpoints); 
	 HadoopUtil.overwriteOutput(output); 
	 // 将測試點集寫入輸入目錄下
 SimpleDataSet.writePointsToFile(testpoints); 

 // 調用 CanopyDriver.buildClusters 的方法執行 Canopy 聚類,參數是:
	 // 	 1. 輸入路徑,輸出路徑
	 // 	 2. 計算距離的方法
	 // 	 3. 距離門檻值 T1 和 T2 
	 new CanopyDriver().buildClusters(testpoints, output, measure, T1, T2, true); 
	 // 列印 Canopy 聚類的結果
	 List<List<Cluster>> clustersM = DisplayClustering.loadClusters(output);
	 	 List<Cluster> clusters = clustersM.get(clustersM.size()-1); 
	 if(clusters != null){ 
 for(Cluster canopy : clusters) { 
    System.out.println("Cluster id: " + canopy.getId() + 
" center: " + canopy.getCenter().asFormatString()); 
   System.out.println("       Points: " + canopy.getNumPoints());
   		 } 
	 } 
 } 

執行結果
 Canopy Clustering In Memory Result 
 Cluster id: 0 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector",
 "vector":"{\"values\":{\"table\":[0,1,0],\"values\":[1.8,1.8,0.0],
 \"state\":[1,1,0],\"freeEntries\":1,\"distinct\":2,\"lowWaterMark\":0,
 \"highWaterMark\":1,\"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},
 \"size\":2,\"lengthSquared\":-1.0}"} 
       Points: 5 
 Cluster id: 1 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector",
 "vector":"{\"values\":{\"table\":[0,1,0],\"values\":[7.5,7.666666666666667,0.0],
 \"state\":[1,1,0],\"freeEntries\":1,\"distinct\":2,\"lowWaterMark\":0,
 \"highWaterMark\":1,\"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},\"size\":2,
 \"lengthSquared\":-1.0}"} 
       Points: 6 
 Cluster id: 2 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector",
 "vector":"{\"values\":{\"table\":[0,1,0],\"values\":[5.0,5.5,0.0],
 \"state\":[1,1,0],\"freeEntries\":1,\"distinct\":2,\"lowWaterMark\":0,
 \"highWaterMark\":1,\"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},\"size\":2,
 \"lengthSquared\":-1.0}"} 
       Points: 2 

 Canopy Clustering Using Map/Reduce Result 
 Cluster id: 0 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector", 
 "vector":"{\"values\":{\"table\":[0,1,0],\"values\":[1.8,1.8,0.0],
 \"state\":[1,1,0],\"freeEntries\":1,\"distinct\":2,\"lowWaterMark\":0,
 \"highWaterMark\":1,\"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},
 \"size\":2,\"lengthSquared\":-1.0}"} 
       Points: 5 
 Cluster id: 1 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector",
 "vector":"{\"values\":{\"table\":[0,1,0],\"values\":[7.5,7.666666666666667,0.0],
 \"state\":[1,1,0],\"freeEntries\":1,\"distinct\":2,\"lowWaterMark\":0,
 \"highWaterMark\":1,\"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},\"size\":2,
 \"lengthSquared\":-1.0}"} 
       Points: 6 
 Cluster id: 2 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector", 
 "vector":"{\"values\":{\"table\":[0,1,0], 
 \"values\":[5.333333333333333,5.666666666666667,0.0],\"state\":[1,1,0],
 \"freeEntries\":1,\"distinct\":2,\"lowWaterMark\":0,\"highWaterMark\":1,
 \"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},\"size\":2,\"lengthSquared\":-1.0}"} 
       Points: 3      

模糊 K 均值聚類算法

模糊 K 均值聚類算法是 K 均值聚類的擴充,它的基本原理和 K 均值一樣,隻是它的聚類結果允許存在對象屬于多個簇,也就是說:它屬于我們前面介紹過的可重疊聚類算法。為了深入了解模糊 K 均值和 K 均值的差別,這裡我們得花些時間了解一個概念:模糊參數(Fuzziness Factor)。

與 K 均值聚類原理類似,模糊 K 均值也是在待聚類對象向量集合上循環,但是它并不是将向量配置設定給距離最近的簇,而是計算向量與各個簇的相關性(Association)。假設有一個向量 v,有 k 個簇,v 到 k 個簇中心的距離分别是 d1,d2… dk,那麼 V 到第一個簇的相關性 u1可以通過下面的算式計算:

探索推薦引擎内部的秘密,第 3 部分: 深入推薦引擎相關算法 - 聚類

計算 v 到其他簇的相關性隻需将 d1替換為對應的距離。

從上面的算式,我們看出,當 m 近似 2 時,相關性近似 1;當 m 近似 1 時,相關性近似于到該簇的距離,是以 m 的取值在(1,2)區間内,當 m 越大,模糊程度越大,m 就是我們剛剛提到的模糊參數。

講了這麼多理論的原理,下面我們看看如何使用 Mahout 實作模糊 K 均值聚類,同前面的方法一樣,Mahout 一樣提供了基于記憶體和基于 Hadoop Map/Reduce 的兩種實作 FuzzyKMeansClusterer 和 FuzzyMeansDriver,分别是清單 5 給出了一個例子。

清單 5. 模糊 K 均值聚類算法示例
public static void fuzzyKMeansClusterInMemory() { 
 // 指定聚類的個數
 int k = 2; 
 // 指定 K 均值聚類算法的最大疊代次數
 int maxIter = 3; 
 // 指定 K 均值聚類算法的最大距離門檻值
 double distanceThreshold = 0.01; 
 // 指定模糊 K 均值聚類算法的模糊參數
 float fuzzificationFactor = 10; 
 // 聲明一個計算距離的方法,這裡選擇了歐幾裡德距離
 DistanceMeasure measure = new EuclideanDistanceMeasure(); 
 // 建構向量集,使用的是清單 1 裡的二維點集
	 List<Vector> pointVectors = SimpleDataSet.getPointVectors(SimpleDataSet.points); 
 // 從點集向量中随機的選擇 k 個作為簇的中心
	 List<Vector> randomPoints = RandomSeedGenerator.chooseRandomPoints(points, k); 
	 // 建構初始簇,這裡與 K 均值不同,使用了 SoftCluster,表示簇是可重疊的
	 List<SoftCluster> clusters = new ArrayList<SoftCluster>(); 
	 int clusterId = 0; 
	 for (Vector v : randomPoints) { 
		 clusters.add(new SoftCluster(v, clusterId++, measure)); 
	 } 
 // 調用 FuzzyKMeansClusterer 的 clusterPoints 方法進行模糊 K 均值聚類
	 List<List<SoftCluster>> finalClusters = 
	 FuzzyKMeansClusterer.clusterPoints(points, 
 clusters, measure, distanceThreshold, maxIter, fuzzificationFactor); 
	 // 列印聚類結果
	 for(SoftCluster cluster : finalClusters.get(finalClusters.size() - 1)) { 
		 System.out.println("Fuzzy Cluster id: " + cluster.getId() + 
" center: " + cluster.getCenter().asFormatString()); 
	 } 
 } 

 public class fuzzyKMeansClusterUsingMapReduce { 
 // 指定模糊 K 均值聚類算法的模糊參數
	 float fuzzificationFactor = 2.0f; 
 // 指定需要聚類的個數,這裡選擇 2 類
	 int k = 2; 
 // 指定最大疊代次數
	 int maxIter = 3; 
 // 指定最大距離門檻值
	 double distanceThreshold = 0.01; 
 // 聲明一個計算距離的方法,這裡選擇了歐幾裡德距離
	 DistanceMeasure measure = new EuclideanDistanceMeasure(); 
 // 設定輸入輸出的檔案路徑
	 Path testpoints = new Path("testpoints"); 
	 Path output = new Path("output"); 
 // 清空輸入輸出路徑下的資料
	 HadoopUtil.overwriteOutput(testpoints); 
	 HadoopUtil.overwriteOutput(output); 
 // 将測試點集寫入輸入目錄下
	 SimpleDataSet.writePointsToFile(testpoints); 
 // 随機的選擇 k 個作為簇的中心
	 Path clusters = RandomSeedGenerator.buildRandom(testpoints, 
 new Path(output, "clusters-0"), k, measure); 
	 FuzzyKMeansDriver.runJob(testpoints, clusters, output, measure, 0.5, maxIter, 1, 
 fuzzificationFactor, true, true, distanceThreshold, true); 
 // 列印模糊 K 均值聚類的結果
	 ClusterDumper clusterDumper = new ClusterDumper(new Path(output, "clusters-" + 
 maxIter ),new Path(output, "clusteredPoints")); 
	 clusterDumper.printClusters(null); 
 } 

執行結果
 Fuzzy KMeans Clustering In Memory Result 
 Fuzzy Cluster id: 0 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector",
 "vector":"{\"values\":{\"table\":[0,1,0],
 \"values\":[1.9750483367699223,1.993870669568863,0.0],\"state\":[1,1,0],
 \"freeEntries\":1,\"distinct\":2,\"lowWaterMark\":0,\"highWaterMark\":1,
 \"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},\"size\":2,\"lengthSquared\":-1.0}"} 
 Fuzzy Cluster id: 1 
 center:{"class":"org.apache.mahout.math.RandomAccessSparseVector",
 "vector":"{\"values\":{\"table\":[0,1,0], 
 \"values\":[7.924827516566109,7.982356511917616,0.0],\"state\":[1,1,0],
 \"freeEntries\":1, \"distinct\":2,\"lowWaterMark\":0,\"highWaterMark\":1,
 \"minLoadFactor\":0.2,\"maxLoadFactor\":0.5},\"size\":2,\"lengthSquared\":-1.0}"} 

 Funzy KMeans Clustering Using Map Reduce Result 
 Weight:  Point: 
	 0.9999249428064162: [8.000, 8.000] 
	 0.9855340718746096: [9.000, 8.000] 
	 0.9869963781734195: [8.000, 9.000] 
	 0.9765978701133124: [9.000, 9.000] 
	 0.6280999013864511: [5.000, 6.000] 
	 0.7826097471578298: [6.000, 6.000] 
	 Weight:  Point: 
	 0.9672607354172386: [1.000, 1.000] 
	 0.9794914088151625: [2.000, 1.000] 
	 0.9803932521191389: [1.000, 2.000] 
	 0.9977806183197744: [2.000, 2.000] 
	 0.9793701109946826: [3.000, 3.000] 
	 0.5422929338028506: [5.000, 5.000]      

狄利克雷聚類算法

前面介紹的三種聚類算法都是基于劃分的,下面我們簡要介紹一個基于機率分布模型的聚類算法,狄利克雷聚類(Dirichlet Processes Clustering)。

首先我們先簡要介紹一下基于機率分布模型的聚類算法(後面簡稱基于模型的聚類算法)的原理:首先需要定義一個分布模型,簡單的例如:圓形,三角形等,複雜的例如正則分布,泊松分布等;然後按照模型對資料進行分類,将不同的對象加入一個模型,模型會增長或者收縮;每一輪過後需要對模型的各個參數進行重新計算,同時估計對象屬于這個模型的機率。是以說,基于模型的聚類算法的核心是定義模型,對于一個聚類問題,模型定義的優劣直接影響了聚類的結果,下面給出一個簡單的例子,假設我們的問題是将一些二維的點分成三組,在圖中用不同的顔色表示,圖 A 是采用圓形模型的聚類結果,圖 B 是采用三角形模型的聚類結果。可以看出,圓形模型是一個正确的選擇,而三角形模型的結果既有遺漏又有誤判,是一個錯誤的選擇。

圖 3 采用不同模型的聚類結果
探索推薦引擎内部的秘密,第 3 部分: 深入推薦引擎相關算法 - 聚類

Mahout 實作的狄利克雷聚類算法是按照如下過程工作的:首先,我們有一組待聚類的對象和一個分布模型。在 Mahout 中使用 ModelDistribution 生成各種模型。初始狀态,我們有一個空的模型,然後嘗試将對象加入模型中,然後一步一步計算各個對象屬于各個模型的機率。下面清單給出了基于記憶體實作的狄利克雷聚類算法。

清單 6. 狄利克雷聚類算法示例
public static void DirichletProcessesClusterInMemory() { 
 // 指定狄利克雷算法的 alpha 參數,它是一個過渡參數,使得對象分布在不同模型前後能進行光滑的過渡
	 double alphaValue = 1.0; 
 // 指定聚類模型的個數
	 int numModels = 3; 
 // 指定 thin 和 burn 間隔參數,它們是用于降低聚類過程中的記憶體使用量的
	 int thinIntervals = 2; 
	 int burnIntervals = 2; 
 // 指定最大疊代次數
	 int maxIter = 3; 
	 List<VectorWritable> pointVectors = 
	 SimpleDataSet.getPoints(SimpleDataSet.points); 
 // 初始階段生成空分布模型,這裡用的是 NormalModelDistribution 
	 ModelDistribution<VectorWritable> model = 
 new NormalModelDistribution(new VectorWritable(new DenseVector(2))); 
 // 執行聚類
	 DirichletClusterer dc = new DirichletClusterer(pointVectors, model, alphaValue, 
 numModels, thinIntervals, burnIntervals); 
	 List<Cluster[]> result = dc.cluster(maxIter); 
 // 列印聚類結果
	 for(Cluster cluster : result.get(result.size() -1)){ 
		 System.out.println("Cluster id: " + cluster.getId() + " center: " + 
 cluster.getCenter().asFormatString()); 
		 System.out.println("       Points: " + cluster.getNumPoints()); 	
	 } 
 } 

執行結果
 Dirichlet Processes Clustering In Memory Result 
 Cluster id: 0 
 center:{"class":"org.apache.mahout.math.DenseVector",
 "vector":"{\"values\":[5.2727272727272725,5.2727272727272725],
 \"size\":2,\"lengthSquared\":-1.0}"} 
       Points: 11 
 Cluster id: 1 
 center:{"class":"org.apache.mahout.math.DenseVector",
 "vector":"{\"values\":[1.0,2.0],\"size\":2,\"lengthSquared\":-1.0}"} 
       Points: 1 
 Cluster id: 2 
 center:{"class":"org.apache.mahout.math.DenseVector",
 "vector":"{\"values\":[9.0,8.0],\"size\":2,\"lengthSquared\":-1.0}"} 
       Points: 0      

Mahout 中提供多種機率分布模型的實作,他們都繼承 ModelDistribution,如圖 4 所示,使用者可以根據自己的資料集的特征選擇合适的模型,詳細的介紹請參考 Mahout 的官方文檔。

圖 4 Mahout 中的機率分布模型層次結構
探索推薦引擎内部的秘密,第 3 部分: 深入推薦引擎相關算法 - 聚類

Mahout 聚類算法總結

前面詳細介紹了 Mahout 提供的四種聚類算法,這裡做一個簡要的總結,分析各個算法優缺點,其實,除了這四種以外,Mahout 還提供了一些比較複雜的聚類算法,這裡就不一一詳細介紹了,詳細資訊請參考 Mahout Wiki 上給出的聚類算法詳細介紹。

表 1 Mahout 聚類算法總結
算法 記憶體實作 Map/Reduce 實作 簇個數是确定的 簇是否允許重疊
K 均值 KMeansClusterer KMeansDriver Y N
Canopy CanopyClusterer CanopyDriver N N
模糊 K 均值 FuzzyKMeansClusterer FuzzyKMeansDriver Y Y
狄利克雷 DirichletClusterer DirichletDriver N Y

回頁首

總結

聚類算法被廣泛的運用于資訊智能處理系統。本文首先簡述了聚類概念與聚類算法思想,使得讀者整體上了解聚類這一重要的技術。然後從實際建構應用的角度出發,深入的介紹了開源軟體 Apache Mahout 中關于聚類的實作架構,包括了其中的數學模型,各種聚類算法以及在不同基礎架構上的實作。通過代碼示例,讀者可以知道針對他的特定的資料問題,怎麼樣向量化資料,怎麼樣選擇各種不同的聚類算法。

本系列的下一篇将繼續深入了解推薦引擎的相關算法 -- 分類。與聚類一樣,分類也是一個資料挖掘的經典問題,主要用于提取描述重要資料類的模型,随後我們可以根據這個模型進行預測,推薦就是一種預測的行為。同時聚類和分類往往也是相輔相成的,他們都為在海量資料上進行高效的推薦提供輔助。是以本系列的下一篇文章将詳細介紹各類分類算法,它們的原理,優缺點和實用場景,并給出基于 Apache Mahout 的分類算法的高效實作。

最後,感謝大家對本系列的關注和支援。

參考資料

學習

  • 聚類分析:Wikipedia 上關于聚類分析的介紹
  • 資料挖掘:概念與技術(韓家偉):關于資料挖掘的經典著作,詳細介紹了資料挖掘領域的各種問題和應用,其中對聚類分析的經典算法也有詳盡的講解。
  • 資料挖掘:實用機器學習技術:同樣是資料挖掘的經典著作,對領域内的算法,算法的發展進行了詳細的介紹。
  • “Apache Mahout簡介” (Grant Ingersoll,developerWorks,2009 年 10 月):Mahout 的創始者 Grant Ingersoll 介紹了機器學習的基本概念,并示範了如何使用 Mahout 來實作文檔叢集、提出建議群組織内容。
  • Apache Mahout:Apache Mahout 項目的首頁,搜尋關于 Mahout 的所有内容。
  • Apache Mahout算法總結:Apache Mahout 的 Wiki 上關于實作算法的詳細介紹。
  • Mahout In Action:Sean Owen 詳細介紹了 Mahout 項目,其中有很大篇幅介紹了 Mahout 提供的聚類算法,并給出一些簡單的例子。
  • TF-IDF:Wikipedia 上關于 TF-IDF 的詳細介紹,包括它的計算方法,優缺點,應用場景等。
  • 路透資料集:路透提供了大量的新聞資料集,可以作為聚類分析的資料源,本文中對文本聚類分析的部分采用了路透“Reuters-21578”資料集
  • Efficient Clustering of High Dimensional Data Sets with Application to Reference Matching,發表于 2000 的 Canopy 算法的論文。
  • 狄利克雷分布:Wikipedia 上關于狄利克雷分布的介紹,它是本文介紹的狄利克雷聚類算法的基礎
  • 基于Apache Mahout建構社會化推薦引擎:筆者 09 年釋出的一篇關于基于 Mahout 實作推薦引擎的 developerWorks 文章,其中詳細介紹了 Mahout 的安裝步驟,并給出一個簡單的電影推薦引擎的例子。
  • 機器學習:機器學習的 Wikipedia 頁面,可幫助您了解關于機器學習的更多資訊。
  • developerWorks Java技術專區:數百篇關于 Java 程式設計各個方面的文章。 
  • developerWorks Web development 專區:通過專門關于 Web 技術的文章和教程,擴充您在網站開發方面的技能。
  • developerWorks Ajax 資源中心:這是有關 Ajax 程式設計模型資訊的一站式中心,包括很多文檔、教程、論壇、blog、wiki 和新聞。任何 Ajax 的新資訊都能在這裡找到。
  • developerWorks Web 2.0 資源中心,這是有關 Web 2.0 相關資訊的一站式中心,包括大量 Web 2.0 技術文章、教程、下載下傳和相關技術資源。您還可以通過 Web 2.0 新手入門 欄目,迅速了解 Web 2.0 的相關概念。
  • 檢視 HTML5 專題,了解更多和 HTML5 相關的知識和動向。

繼續閱讀