聚類演算法之BIRCH(Java實現)
BIRCH(Balanced Iterative Reducing and Clustering using Hierarchies)天生就是為處理超大規模(至少要讓你的記憶體容不下)的資料集而設計的,它可以在任何給定的記憶體下執行。關於BIRCH的更多特點先不介紹,我先講一下演算法的完整實現細節,對演算法的實現過程搞清楚後再去看別人對該演算法的評價才會感受深刻。
你不需要具備B樹的相關知識,我接下來會講得很清楚。
BIRCH演算法的過程就是要把待分類的資料插入一棵樹中,並且原始資料都在葉子節點上。這棵樹看起來是這個樣子:
在這棵樹中有3種類型的節點:Nonleaf、Leaf、MinCluster,Root可能是一種Nonleaf,也可能是一種Leaf。所有的Leaf放入一個雙向連結串列中。每一個節點都包含一個CF值,CF是一個三元組
N=3,
=(1+4+7,2+5+8,3+6+9)=(12,15,18),
=(1+16+49,4+25+64,9+36+81)。
就拿這個MinCluster為例,我們可以計算它的
簇中心
簇半徑
簇直徑
我們還可以計算兩個簇之間的距離,當然你也可以使用D0,D1,D3等等,不過在這裡我們使用D2。
有意思的是簇中心、簇半徑、簇直徑以及兩簇之間的距離D0到D3都可以由CF來計算,比如
簇直徑
簇間距離,這裡的N,LS和SS是指兩簇合並後大簇的N,LS和SS。所謂兩簇合並只需要兩個對應的CF相加那可
CF1 + CF2 = (N1 + N2 , LS1 + LS2, SS1 + SS2)
每個節點的CF值就是其所有孩子節點CF值之和,以每個節點為根節點的子樹都可以看成 是一個簇。
Nonleaf、Leaf、MinCluster都是有大小限制的,Nonleaf的孩子節點不能超過B個,Leaf最多隻能有L個MinCluster,而一個MinCluster的直徑不能超過T。
演算法起初,我們掃描資料庫,拿到第一個data point instance--(1,2,3),我們建立一個空的Leaf和MinCluster,把點(1,2,3)的id值放入Mincluster,更新MinCluster的CF值為(1,(1,2,3),(1,4,9)),把MinCluster作為Leaf的一個孩子,更新Leaf的CF值為(1,(1,2,3),(1,4,9))。實際上只要往樹中放入一個CF(這裡我們用CF作為Nonleaf、Leaf、MinCluster的統稱),就要更新從Root到該葉子節點的路徑上所有節點的CF值。
當又有一個數據點要插入樹中時,把這個點封裝為一個MinCluster(這樣它就有了一個CF值),把新到的資料點記為CF_new,我們拿到樹的根節點的各個孩子節點的CF值,根據D2來找到CF_new與哪個節點最近,就把CF_new加入那個子樹上面去。這是一個遞迴的過程。遞迴的終止點是要把CF_new加入到一個MinCluster中,如果加入之後MinCluster的直徑沒有超過T,則直接加入,否則譔CF_new要單獨作為一個簇,成為MinCluster的兄弟結點。插入之後注意更新該節點及其所有祖先節點的CF值。
插入新節點後,可能有些節點的孩子數大於了B(或L),此時該節點要分裂。對於Leaf,它現在有L+1個MinCluster,我們要新建立一個Leaf,使它作為原Leaf的兄弟結點,同時注意每新建立一個Leaf都要把它插入到雙向連結串列中。L+1個MinCluster要分到這兩個Leaf中,怎麼分呢?找出這L+1個MinCluster中距離最遠的兩個Cluster(根據D2),剩下的Cluster看離哪個近就跟誰站在一起。分好後更新兩個Leaf的CF值,其祖先節點的CF值沒有變化,不需要更新。這可能導致祖先節點的遞迴分裂,因為Leaf分裂後恰好其父節點的孩子數超過了B。Nonleaf的分裂方法與Leaf的相似,只不過產生新的Nonleaf後不需要把它放入一個雙向連結串列中。如果是樹的根節點要分裂,則樹的高度加1。
CF.java
package birch;
public class CF
{
private int N;
private double []
LS;
private double []
SS;
public CF()
{
LS= new double [BIRCH.dimen];
SS= new double [BIRCH.dimen];
}
//
根據一個data point instance建立一個Clustering Feature
public CF( double []
data) {
int len
= data.length;
this .N
= 1 ;
this .LS
= data;
this .SS= new double [len];
for ( int i
= 0 ;
i < len; i++)
this .SS[i]
= Math.pow(data[i], 2 );
}
//複製建構函式(深複製)
public CF(CF
cf){
this .N=cf.getN();
int len=cf.getLS().length;
this .LS= new double [len];
this .SS= new double [len];
for ( int i=
|