KMeans聚類演算法
1、什麼是聚類
所謂聚類就是將一組物件按照特徵劃分不為的小組,使得組內的差異性儘可能的小,組間的差異儘可能的大。例如,粗粒度的分類,按照學校實力,分為985、211高校,普通一本高校,二本高校,三本高校。如果再更加細的分類,一個學校裡面會按照所修的課程差異性分為不同學院,不同專業,這些同學院的專業課相差較小,不同的學院的課程相差就很大了。
2、聚類與分類的區別
分類演算法是已知資料的類別,通過對樣本資料的學習建立分類模型,屬於監督學習;而聚類演算法是不知道資料的類別,由演算法根據資料的分佈進行分類,屬於非監督學習模型。
3、Kmeans聚類
Kmeans聚類屬於劃分聚類,由使用者指定聚類簇的個數k,尋找k個簇中心,按照指定的相似度計算方法將樣本劃分到這K個聚類中心。
這裡的相似度計算有很多種方法,常用的有
歐式距離:
餘弦相似度:
下面全部採用歐式距離作為相似度度量方法
演算法流程:
隨機初始化k個聚類中心(第一次的聚類中心是隨機選取的k個樣本)
對每個樣本資料
對每個聚類中心
計算樣本與聚類中心的相似度
將資料點分配到相似度最高的樣本中心
對每個簇,使用簇內的樣本資料特徵均值更新聚類中心
重複上述步驟,直至迭代次數達到使用者指定的次數或者聚類中心基本沒有改變。
Kmeans聚類演算法的優化目標函式:
上式的xi為第i個樣本點,ci為第i個樣本對應的聚類。對所有的樣本,尋找c箇中心點μ,使得每個樣本距離其中心點的歐式距離之和最小。這個函式不是凸函式,無法對其求最優化,因此在聚類的過程中無法保證求得的聚類中心為全域性最優的結果。但是可以確定的是t次迭代的過程,使得這個函式的函式值越來越小。其中隱含著EM演算法的思想,將所有的樣本分配到最近的聚類中心屬於E環節求期望,對每個簇的中心進行修正屬於M環節求最小化。為了避免區域性最優,通常的做法是重複進行多次聚類,每次選擇不同的初始聚類中心,比較選取最優的聚類結果。
4、Kmeans聚類演算法的改進Kmedoids演算法
Kmeans聚類演算法中心的更新採用均值,這種方法計算簡單,但是也會帶來問題,對異常點會比較敏感,計算平均值時會使聚類中心的位置偏向異常點。一種對Kmeans的改進是,對聚類中心的位置更改限制為樣本點。即每次修改不是求樣本的均值,而是將簇內的每一個樣本點嘗試作為這個簇的新中心,選擇一個使得簇內差異最小的樣本點作為新的聚類中心點,對每個簇進行同樣的更新。這種方式對異常點不敏感,但是代價比較高,計算速度,收斂速度都會下降。
5、Kmeans聚類演算法的改進Kmeans++演算法
Kmeans聚類對初始點的選取非常敏感,在大資料集上,同一個演算法不同的初始化點得到的結果往往不同。Kmeans++演算法改進了Kmeans演算法對初始聚類中心的選取方法,聚類過程和Kmeans是一樣的。Kmeans++演算法選取的方式是,首先從樣本點中隨機一個樣本點作為第一個聚類中心點。計算訓練樣本距離各自簇中心的距離,將這個距離儲存在一個dists陣列中,並求距離總和sum,在0至sum區間產生一個隨機數dist,使用這個隨機數從i=0開始做運算dist-=dists[i],直至dist<0,此時的下標i對應的樣本點作為下一個聚類中心點。重複上述步驟,直至選出k個聚類中心點。可以將距離理解為一節線段,陣列dists儲存了這個線段集合的每個線段長度,而sum則是將這些線段進行了一個拼接,在這個拼接的大線段上面隨機選取一個點,那麼這個點落在較長的小線段片段上機率更大。這種做法選取的新的中心點不是距離已有的中心點最遠的點,而是偏向於較遠的點。
6、實現
下面給出了Kmeans、Kmeans++和Kmoids演算法的實現
#_*_encoding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt
class KMeans:
def __init__(self,n_clusters=3,iterNum=200,type='kmeans',type2='random'):
self.k=n_clusters
self.iterNum=iterNum
self.type=type
self.type2=type2
def distCal(self,x,y): #計算歐式距離
return np.sqrt(np.sum(np.power(x-y,2)))
def randCent(self): #隨機初始化聚類中心點
self.centroids=np.mat(np.zeros((self.k,self.n)))
randIndex=np.random.randint(0,self.m,self.k)
for i in range(self.k):
self.centroids[i,:]=self.train[randIndex[i],:]
def Kmeans_plus(self): #Kmeans++初始化聚類中心點
self.centroids=np.mat(np.zeros((self.k,self.n)))
firstCent=np.random.randint(0,self.m)
i=0
self.centroids[i,:]=self.train[firstCent,:]
while i<self.k-1:
self.clusterAssment=self.calClusterAssment(self.train,self.centroids[0:i+1,:])
distSum=sum(self.clusterAssment[:,1])
randNum=np.random.randint(0,distSum)
index=0
while randNum>0:
randNum-=self.clusterAssment[index,1]
index=index+1
i=i+1
self.centroids[i,:]=self.train[index-1,:]
def fit_predict(self,train): #學習資料,返回聚類結果
self.train=np.mat(train)
self.m,self.n=np.shape(self.train)
if self.type2=='random':
self.randCent()
else:
self.Kmeans_plus()
if self.type=='kmeans':
self.KMeans_cluster()
else:
self.Kmedoids_cluster()
return np.reshape(self.clusterAssment[:,0],(1,np.shape(self.clusterAssment[:,0])[0]))
def KMeans_cluster(self): #Kmneas聚類
iter=0
while iter<self.iterNum:
self.clusterAssment=self.calClusterAssment(self.train,self.centroids)
for j in range(self.k):
clust=self.train[np.nonzero(self.clusterAssment[:,0].A==j)[0]]
self.centroids[j,:]=np.mean(clust,axis=0)
iter=iter+1
def calClusterAssment(self,train,centers): #分配
clusterAssment = np.mat(np.zeros((self.m, 2)))
for i in range(self.m):
minDist = np.inf
minIndex = -1
for j in range(len(centers)):
dist = self.distCal(self.train[i, :], centers[j, :])
if dist < minDist:
minDist = dist
minIndex = j
clusterAssment[i, 0] = int(minIndex)
clusterAssment[i, 1] = minDist
return clusterAssment
def Kmedoids_cluster(self): #Kmoids聚類
iter=0
distSum=np.zeros((self.m,1))
while iter<self.iterNum:
self.clusterAssment=self.calClusterAssment(self.train,self.centroids)
for i in range(self.k):
clustIndex=np.nonzero(self.clusterAssment[:,0].A==i)[0]
for j in clustIndex:
distSum[j]=self.calClusterDist(self.train[clustIndex,:],self.train[j,:])
minDist=np.inf ; minIndex=-1
for j in clustIndex:
if distSum[j]<minDist:
minDist=distSum[j]
minIndex=j
self.centroids[i,:]=self.train[minIndex,:]
iter+=1
def calClusterDist(self,train,center): #計算簇內的距離和
dists=0
for i in range(np.shape(train)[0]):
dists+=self.distCal(train[i,:],center)
return dists
def show(self,num=0): #資料顯示
y=self.clusterAssment[:,0]
fig=plt.figure(num)
cValue = ['r', 'y', 'g', 'b', 'r', 'y', 'g', 'b', 'r']
mValue = ['*', '+','s','^','D']
for i in range(self.m):
plt.scatter(self.train[i,1],self.train[i,2],marker=mValue[int(y[i,0])],c=cValue[int(y[i,0])])
for i in range(self.k):
plt.scatter(self.centroids[i,1],self.centroids[i,2],c='r',marker='D')
plt.show()
from sklearn import datasets
iris=datasets.load_iris()
x=iris.data
x=np.mat(x)
#x=np.row_stack((x,[6,6,6,6]))
km=KMeans(n_clusters=4,iterNum=50,type='kmeans',type2='kmeans++')
km.fit_predict(x)
km.show()
上面採用的鳶尾花資料集加載於sklearn包中。
原始資料標籤的分佈如下圖所示:
設定K=2,選擇Kmeans演算法進行聚類,結果如下:
設定k=4進行聚類:
可以看出不同的k,得到的聚類簇大小是不一樣。在實際使用中選擇合適的k值很重要,需要根據具體需求來進行選擇(並不是K越大越好)。
選擇K值為3,Kmeans的第一次結果:
Kmeans的第二次結果:
,
從上面看到,這種隨機初始化聚類中心點,多次執行結果明顯不一樣。
下面是加入異常點使用Kmeans++初始化聚類中心點效果:
異常點在右上側,雖然不是很明顯,但是依舊可以看出,聚類中心點相對於沒有加入異常點向右發生偏移。
使用Kmedoids演算法可以減少異常點對聚類中心的影響,下面給出使用Kmedoids演算法的效果。
接下來在資料中加入一個異常點,來看看聚類中心是否發生偏移
可以看到上面兩張圖的顯示除了新加入了一個樣本點之外,聚類中心沒有發現變化。也就是說,Kmoids演算法對這種異常點並不敏感。
7、總結
Kmeans演算法是一種高效的聚類演算法,易理解易實現,用於發現沒有給出標籤的資料內部的結構,屬於無監督學習。時間複雜度和資料的規模m,迭代次數t,聚類個數k成正比,時間複雜度為O(kmt)。但是Kmeans演算法要求資料必須是數值型資料,如果是其它型別資料則需要進行轉換。Kmeans演算法對異常點敏感,對初始聚類中心的選取敏感,因此有對應的改進演算法Kmedoids和Kmeans++演算法,這類演算法都只適合於發現球型簇,不能用於發現非凸型簇。