- - 概述 - -
聚類分析最簡單、最基本的版本是劃分,它把對象組織成多個互斥的組或簇。爲了使得問題說明簡潔,我們假定簇的個數作爲背景知識給定。這個參數是劃分方法的起點。
形式地,給定 n 個數據對象的數據集 D,以及要生成的簇個數 k,劃分方法把數據對象組織成 k ( k <= n )個分區,其中每個分區代表一個簇。這些簇的形成旨在優化一個客觀劃分準則,如基於距離的相異性函數,使得根據數據集的屬性,在同一個簇中的對象是“相似的”,而不同的簇中的對象是“相異的”。
1、k-均值:一種基於形心的技術
1.1、k-均值算法簡介
假設數據集 D 包含 n 個歐式空間中的對象。劃分方法把 D 中的對象分配到 k 個簇。一個目標函數用來評估劃分的質量,使得簇內對象相互相似,而與其他簇中的對象相異。也就是說,該目標函數以簇內高相似性和簇間低相似性爲目標。
基於形心的劃分技術使用簇Ci的形心代表該簇。從概念上講,簇的形心是它的中心點。形心可以用多種方法定義,例如用分配給該簇的對象(或點)的均值或中心點定義。對象p∈Ci 與該簇的代表ci之差用dist(p, ci)度量,其中dist(x, y)是兩個點 x 和 y 之間的歐式距離。簇 Ci 的質量可以用簇內變差度量,它是 Ci 中所有對象和形心ci之間的誤差的平方和,定義爲:
(公式-1)
其中,E是數據集中所有對象的誤差的平方和;p是空間中的點,表示給定的對象;ci是簇Ci的形心(p和ci都是多維的)。換言之,對於每個簇中的每個對象,求對象到簇中心距離的平方,然後求和。這個目標函數試圖使生成的結果簇儘可能緊湊和獨立。
優化簇內變差是一項具有挑戰性的計算任務。在最壞情況下,我們必須枚舉大量可能的劃分(是簇數的指數),並檢查簇內變差值。業已證明,在一般的歐式空間中,即便對於兩個簇(即 k= 2),該問題也是NP-hard(註釋1)的。此外,即便在二位歐式空間中,對於一般的簇個數k,該問題也是NP-hard的。如果簇個數k和空間維度d固定,則該問題可以在時間內求解,其中n是對象的個數。爲了克服求精度解的巨大計算開銷,實踐中通常需要使用貪心方法。一個基本的例子是k-均值算法,它簡單並且經常使用。
1.2、k-均值算法
k-均值算法把簇的形心定義爲簇內點的均值。它的處理流程如下。首先,在D中隨機地選擇k個對象,每個對象代表一個簇的初始均值或中心。對剩下的每個對象,根據其與各個簇中心的歐式距離,將它分配到最相似的簇。然後,k-均值算法迭代地改善簇內變差。對於每個簇,它使用上次迭代分配到該簇的對象,計算心的均值。然後,使用更新後的均值作爲新的簇的中心,重新分配所有的對象。迭代繼續,指導分配穩定,即本輪形成的簇與前一輪形成的簇相同。k-均值算法過程概括如下:
1.3、k-均值算法分析
不能保證k-均值算法收斂於全局最優解,並且它常常終止於一個局部最優解。結果可能依賴於初始簇中心的隨機選擇。實踐中,爲了得到好的結果,通常以不同的初始簇中心,多次運行k-均值算法。
k-均值算法的複雜度是O(ktmn),其中n是對象總數,k是簇數,t是迭代次數,m是維數。通常,k遠小於n並且t遠小於n。空間複雜度:O((m+K)n),其中,K爲簇的數目,n爲記錄數,m爲維數。因此,對於處理大數據集,該算法是相對可伸縮的和有效的。
k-均值算法有一些變種。他們可能在初始k個均值的選擇、相異度的計算、簇均值的計算策略上有所不同。僅當簇的均值有定義時才能使用k-均值算法。在某些應用中,當涉及具有標稱屬性的數據時,均值可能無定義。k-衆數(k-modes)算法是k-均值算法的一個變體,它擴展了k-均值算法,用簇對象的衆數取代簇對象的均值。它採用新的相異性度量來處理標稱對象,採用基於頻率的方法來更新簇的衆數。可以混合k-均值和k-衆數算法,對混合了數值和標稱值得數據進行聚類。
要求用戶事先給出簇數k是k-均值算法的一個缺點。然而,針對如何克服這一缺點已經有一些研究,如提供k值的近似範圍,然後使用分析技術,通過比較由不同k得到的聚類結果,確定最佳的k值。k-均值算法不適合於發現非凸形狀的簇,或大小差別很大的簇。此外,它對噪聲和離羣點敏感,因爲少量的這類數據能夠對均值產生極大的影響。
如何提高k-均值算法的可伸縮性:一種是k-均值算法在大型數據集上更有效的方法是在聚類時使用合適規模的樣本。另一種是使用過濾方法,使用空間層次數據索引節省計算均值的開銷。第三種方法是利用微聚類的思想,首先把鄰近的對象劃分到一些”微簇”(microcluster)中,然後對這些微簇使用k-均值方法進行聚類。
1.4 k-means算法的實現
使用的Python是2.6.6版本的。附加的庫有Numpy和Matplotlib。
文件:kmeans.py
#-*- coding: UTF-8 -*-
#################################################
# filename:kmeans.py
# kmeans: k-means 聚類算法實現
# 該段代碼來自博客:機器學習算法與Python實踐之(五)k均值聚類(k-means)
# 博客地址:http://blog.csdn.net/zouxy09/article/details/17589329
#################################################
from numpy import *
import matplotlib.pyplot as plt
# calculate Euclidean distance
def euclDistance(vector1, vector2):
return sqrt(sum(power(vector2 - vector1, 2)))
# init centroids with random samples
def initCentroids(dataSet, k):
numSamples, dim = dataSet.shape
centroids = zeros((k, dim))
for i in range(k):
index = int(random.uniform(0, numSamples))
centroids[i, :] = dataSet[index, :]
return centroids
# k-means cluster
def kmeans(dataSet, k):
numSamples = dataSet.shape[0]
# first column stores which cluster this sample belongs to,
# second column stores the error between this sample and its centroid
clusterAssment = mat(zeros((numSamples, 2)))
clusterChanged = True
## step 1: init centroids
centroids = initCentroids(dataSet, k)
while clusterChanged:
clusterChanged = False
## for each sample
for i in xrange(numSamples):
minDist = 100000.0
minIndex = 0
## for each centroid
## step 2: find the centroid who is closest
for j in range(k):
distance = euclDistance(centroids[j, :], dataSet[i, :])
if distance < minDist:
minDist = distance
minIndex = j
## step 3: update its cluster
if clusterAssment[i, 0] != minIndex:
clusterChanged = True
clusterAssment[i, :] = minIndex, minDist**2
## step 4: update centroids
for j in range(k):
pointsInCluster = dataSet[nonzero(clusterAssment[:, 0].A == j)[0]]
centroids[j, :] = mean(pointsInCluster, axis = 0)
print 'Congratulations, cluster complete!'
return centroids, clusterAssment
# show your cluster only available with 2-D data
def showCluster(dataSet, k, centroids, clusterAssment):
numSamples, dim = dataSet.shape
if dim != 2:
print "Sorry! I can not draw because the dimension of your data is not 2!"
return 1
mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
if k > len(mark):
print "Sorry! Your k is too large! please contact Zouxy"
return 1
# draw all samples
for i in xrange(numSamples):
markIndex = int(clusterAssment[i, 0])
plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex])
mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', '<b', 'pb']
# draw the centroids
for i in range(k):
plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize = 12)
plt.show()
文件:test_kmeans.py
#-*- coding: UTF-8 -*-
#################################################
# filename:test_kmeans.py
# kmeans: k-means 聚類算法測試
# 該段代碼來自博客:機器學習算法與Python實踐之(五)k均值聚類(k-means)
# 博客地址:http://blog.csdn.net/zouxy09/article/details/17589329
# 注意:在運行代碼的時候發現,數據文件的'\t'、模塊引入有問題、無用地引入模塊
#################################################
from numpy import mat
import kmeans
## step 1: 數據載入
print "step 1: load data..."
dataSet = []
fileIn = open('/root/Desktop/testSet.txt')
for line in fileIn.readlines():
lineArr = line.strip().split('\t')
dataSet.append([float(lineArr[0]), float(lineArr[1])])
## step 2: 算法執行
print "step 2: clustering..."
dataSet = mat(dataSet)
k = 4
centroids, clusterAssment = kmeans.kmeans(dataSet, k)
## step 3: 打印結果
print "step 3: show the result..."
kmeans.showCluster(dataSet, k, centroids, clusterAssment)
文件:testSet.txt
1.658985 4.285136
-3.453687 3.424321
4.838138 -1.151539
-5.379713 -3.362104
0.972564 2.924086
-3.567919 1.531611
0.450614 -3.302219
-3.487105 -1.724432
2.668759 1.594842
-3.156485 3.191137
3.165506 -3.999838
-2.786837 -3.099354
4.208187 2.984927
-2.123337 2.943366
0.704199 -0.479481
-0.392370 -3.963704
2.831667 1.574018
-0.790153 3.343144
2.943496 -3.357075
-3.195883 -2.283926
2.336445 2.875106
-1.786345 2.554248
2.190101 -1.906020
-3.403367 -2.778288
1.778124 3.880832
-1.688346 2.230267
2.592976 -2.054368
-4.007257 -3.207066
2.257734 3.387564
-2.679011 0.785119
0.939512 -4.023563
-3.674424 -2.261084
2.046259 2.735279
-3.189470 1.780269
4.372646 -0.822248
-2.579316 -3.497576
1.889034 5.190400
-0.798747 2.185588
2.836520 -2.658556
-3.837877 -3.253815
2.096701 3.886007
-2.709034 2.923887
3.367037 -3.184789
-2.121479 -4.232586
2.329546 3.179764
-3.284816 3.273099
3.091414 -3.815232
-3.762093 -2.432191
3.542056 2.778832
-1.736822 4.241041
2.127073 -2.983680
-4.323818 -3.938116
3.792121 5.135768
-4.786473 3.358547
2.624081 -3.260715
-4.009299 -2.978115
2.493525 1.963710
-2.513661 2.642162
1.864375 -3.176309
-3.171184 -3.572452
2.894220 2.489128
-2.562539 2.884438
3.491078 -3.947487
-2.565729 -2.012114
3.332948 3.983102
-1.616805 3.573188
2.280615 -2.559444
-2.651229 -3.103198
2.321395 3.154987
-1.685703 2.939697
3.031012 -3.620252
-4.599622 -2.185829
4.196223 1.126677
-2.133863 3.093686
4.668892 -2.562705
-2.793241 -2.149706
2.884105 3.043438
-2.967647 2.848696
4.479332 -1.764772
-4.905566 -2.911070
運行結果:
不同的簇用不同的顏色來表示,其中的大菱形是對應類的均值質心點。
2、k-中心點:一種基於代表對象的算法
2.1、k-中心點算法簡介
k-均值算法對離羣點敏感,因爲這種對象遠離大多數數據,因此分配到一個簇時,他們可能嚴重地扭曲簇的均值。這不經意間影響了其他對象到簇的分配。正如在公式1中觀察到地 ,公式1中平方誤差函數的使用更是嚴重惡化了這一影響。
例: k-均值的缺點。考慮一維空間的7個點,他們的值分別是1、2、3、8、9、10、25。直觀地,通過視覺觀察,我們猜想這些點劃分成簇{1,2,3}和{8,9,10},其中點25被排除,因爲它看上去是一個離羣點。k-均值如何劃分這些值呢?如果我們以k=2和公式1使用k-均值,劃分{{1,2,3},{8,9,10,25}}具有簇內變差
其中,簇{1,2,3}的均值爲2,簇{8,9,10,25}的均值爲13。把這一劃分與劃分{{1,2,3,8},{9,10,25}}比較,後者的簇內變差爲189.67.後者的簇內變差比前者小,因此,由於離羣點25的緣故,k-均值方法把8分配到到不同於9和10所在的簇。此外,第二個簇的中心點爲14.67,顯著的偏離簇中的所有成員。
如何優化k-均值算法,降低它對離羣點的敏感性呢?我們可以不採用簇中對象的均值作爲參照點,而是挑選實際對象來代表簇,每個簇使用一個代表對象。其餘的每個對象被分配到與其最爲相似的代表性對象所在的簇中。於是,劃分方法基於最小化所有對象p與其對應的代表對象之間的相異度只和的原則來進行劃分。確切的說,使用了一個絕對誤差標準,其定義如下:
(公式2)
其中,E是數據集中所有對象p與Ci的代表對象Oi的絕對誤差只和。這是k-中心點(k-medoids)方法的基礎。k-中心點聚類通過最小化該絕對誤差(公式2),把n個對象劃分到k個簇中。
當k=1時,我們可以在O(n^2)時間內找出準確的中位數。然而,當k是一般的正整數時,k-中心點問題是NP-困難的。
2.2、k-中心點算法實現
圍繞中心點劃分(Partition Around Medoids,PAM)算法是k-中心點聚類的一種流行的實現。它用迭代、貪心的方法處理該問題。與k-均值算法一樣,初始對象(稱作種子)任意選取。我們考慮用一個非代表對象替換一個代表對象是否能夠提高聚類質量。嘗試所有可能的替換。繼續用其他對象替換代表對象的迭代過程,直到結果聚類的質量不可能被任何替換提高。質量用對象與其簇中代表的平均相異度的代價函數度量 。
具體地說,設是當前代表對象(即中心點)的集合。爲了決定一個非代表對象是否是一個當前中心點Oj(1 <=j<=k)的好的替代,我們計算每個對象p到集合中最近對象的距離,並使用該距離更新代價函數。對象重新分配到中是簡單的。假設對象p當前被分配到中心點Oj代表的簇中(圖a 或 圖 b)。在Oj被置換後,我們需要把p重新分配到不同的簇嗎?對象p需要重新分配,被分配到或者其他Oi(i不等於j)代表的簇,取決於哪個最近。例如,在圖a中,p離Oi最近,因此他被重新到Oi。然而,在圖b中,p離最近,因此它被重新分配到。要是p當前被分配到其他對象Oi(i不等於j)代表的簇中怎麼辦(如圖c)。否則,p被重新分配到(如圖d)。
每當重新分配時,絕對誤差E的差對代價函數有影響。因此,如果一個當前的代表對象被非代表所取代,則代價函數就計算絕對誤差值的差。交換的總代價是所有非代價對象所產生的代價之和。如果總代價爲負,則實際的絕對誤差E將會減小,Oj可以被取代或交換。如果總代價爲正,則認爲當前的代表對象Oj是可接受的,在本次迭代中沒有變化發生。
2.3、替換髮生的條件
每當重新分配發生時,絕對誤差E的差對代價函數有影響。因此,如果一個當前的代表對象被非代表對象所取代,則代價函數就計算絕對誤差值得差。交換的總代價是所有非代價對象所產生的代價之和。如果總代價爲負,則實際的絕對誤差E將會減小,Oj可以被取代或替換。如果總代價爲正,則認爲當前的代表對象Oj是可接受的,在本次迭代中沒有發生變化發生。[註釋2]
2.4、魯棒性比較
k-均值算法與k-中心點算法的魯棒性比較:當存在噪聲或者離羣點時,k-中心點算法比k-均值算法更具魯棒性,這是因爲中心點不像均值那樣容易受離羣點或其他極端值得影響。然而,k-中心點算法的每次迭代的複雜度是O(k(n-k)^2)。當n和k的值較大時,這種計算的開銷非常大,遠高於k-均值算法O(nkmt)。這兩種算法都要求用戶指定簇數k。
2.5、k-中心點算法縮放性
k-中心點算法在小型數據集上運行良好,但是不能很好的運行於大型的數據集。爲了處理大型數據集,可以在大數據集上抽樣得到一個隨機樣本,在樣本空間上進行k-中心點聚類。
註釋
[1]NP-hard:其中,NP是指非確定性多項式(non-deterministic polynomial,縮寫NP)。所謂的非確定性是指,可用一定數量的運算去解決多項式時間內可解決的問題。NP 問題通俗來說是其解的正確性能夠被“很容易檢查”的問題,這裏“很容易檢查”指的是存在一個多項式檢查算法。相應的,若NP中所有問題到某一個問題是圖靈可歸約的,則該問題爲NP困難問題。
[2]在K中心點算法中,每次迭代後的質點都是從聚類的樣本點中選取,而選取的標準就是當該樣本點成爲新的質點後能提高類簇的聚類質量,使得類簇更緊湊。該算法使用絕對誤差標準來定義一個類簇的緊湊程度。
(p是空間中的樣本點,Oj是類簇Cj的質點)
如果某樣本點成爲質點後,絕對誤差能小於原質點所造成的絕對誤差,那麼K中心點算法認爲該樣本點是可以取代原質點的,在一次迭代重計算類簇質點的時候,我們選擇絕對誤差最小的那個樣本點成爲新的質點。
例:
樣本點A –>E1=10
樣本點B –>E2=11
樣本點C –>E3=12
原質點O–>E4=13,那我們選舉A作爲類簇的新質點。