《機器學習實戰》kMeans演算法(K均值聚類演算法)

Thinkgamer_gyt發表於2015-10-07

============================================================================================
《機器學習實戰》系列部落格是博主閱讀《機器學習實戰》這本書的筆記,包含對其中演算法的理解和演算法的Python程式碼實現

另外博主這裡有機器學習實戰這本書的所有演算法原始碼和演算法所用到的原始檔,有需要的留言
============================================================================================


Scikit-learn 實現的K-Means 演算法請參考 :點選閱讀

二分K-Means演算法請參考:點選閱讀


        機器學習中有兩類的大問題,一個是分類,一個是聚類。分類是根據一些給定的已知類別標號的樣本,訓練某種學習機器,使它能夠對未知類別的樣本進行分類。這屬於supervised learning(監督學習)。而聚類指事先並不知道任何樣本的類別標號,希望通過某種演算法來把一組未知類別的樣本劃分成若干類別,這在機器學習中被稱作 unsupervised learning (無監督學習)。在本文中,我們關注其中一個比較簡單的聚類演算法:k-means演算法。 

        k-means演算法是一種很常見的聚類演算法,它的基本思想是:通過迭代尋找k個聚類的一種劃分方案,使得用這k個聚類的均值來代表相應各類樣本時所得的總體誤差最小。

其Python實現的程式碼如下:

#encoding:utf-8
from numpy import *

def loadDataSet(filename):
	dataMat = []          #建立元祖
	fr = open(filename)
	for line in fr.readlines():
		curLine = line.strip().split("\t")
		fltLine = map(float,curLine) #使用map函式將curLine裡的數全部轉換為float型
		dataMat.append(fltLine)
	return dataMat

def distEclud(vecA,vecB):          #計算兩個向量的歐式距離
	return sqrt(sum(power(vecA-vecB,2)))

def randCent(dataSet,k):            #位給定資料集構建一個包含k個隨機質心的集合
	n = shape(dataSet)[1]   #shape函式此時返回的是dataSet元祖的列數
	centroids = mat(zeros((k,n)))       #mat函式建立k行n列的矩陣,centroids存放簇中心
	for j in range(n):
		minJ = min(dataSet[:,j])           #第j列的最小值
		rangeJ = float(max(dataSet[:,j]) - minJ)
		centroids[:,j] = minJ + rangeJ * random.rand(k,1)  #random.rand(k,1)產生shape(k,1)的矩陣
	return centroids

def kMeans(dataSet,k,disMeas = distEclud,createCent = randCent):
	m = shape(dataSet)[0] #shape函式此時返回的是dataSet元祖的行數
	clusterAssment = mat(zeros((m,2)))      #建立一個m行2列的矩陣,第一列存放索引值,第二列存放誤差,誤差用來評價聚類效果
	centroids = createCent(dataSet,k)  #建立k個質心,呼叫createCent()函式
	clusterChanged =True #標誌變數,若為true則繼續迭代
	print "質心位置更新過程變化:"
	while clusterChanged:
		clusterChanged = False
		for i in range(m):
			minDist = inf #inf為正無窮大
			minIndex = -1  #建立索引
			for j in range(k):
				#尋找最近的質心
				disJI = disMeas(centroids[j,:],dataSet[i,:]) #計算每個點到質心的歐氏距離
				if disJI(array([0, 0, 1]), array([0, 2, 0]))
				#print array(nonzero(b2))
				#=>[[0, 0, 1],[0, 2, 0]]
				centroids[cent,:] = mean(ptsInClust,axis=0)  #計算所有點的均值,選項axis=0表示沿矩陣的列方向進行均值計算
	return centroids,clusterAssment  #返回所有的類質心與點分配結果
	

datMat = mat(loadDataSet('data.txt'))
myCentroids,clustAssing = kMeans(datMat,2)
print "最終質心:\n",myCentroids
print "索引值和均值:\n",clustAssing

  k-means演算法比較簡單,但也有幾個比較大的缺點:
1)k值的選擇是使用者指定的,不同的k得到的結果會有挺大的不同,如下圖所示,左邊是k=3的結果,這個就太稀疏了,藍色的那個簇其實是可以再劃分成兩個簇的。而右圖是k=5的結果,可以看到紅色菱形和藍色菱形這兩個簇應該是可以合併成一個簇的:

2)對k個初始質心的選擇比較敏感,容易陷入區域性最小值。例如,我們上面的演算法執行的時候,有可能會得到不同的結果,如下面這兩種情況。K-means也是收斂了,只是收斂到了區域性最小值:

3)存在侷限性,如下面這種非球狀的資料分佈就搞不定了

4)資料庫比較大的時候,收斂會比較慢.

  K均值聚類中簇的值k是使用者預先定義的一個引數,那麼使用者如何才能知道k的選擇是否正確?如何才能知道生成的簇比較好?在計算的過程中保留了每個點的誤差,即該點到簇質心的距離平方值,下面將討論利用該誤差來評價聚類質量好壞的方法,引入度量聚類效果的指標SSE(sum of squared Error,誤差平方和),SSE值越小,越接近於他們的質心,聚類效果也越好,有一種可以肯定減小SSE值得方法是增加k的數目,但這個違背了聚類的目標,聚類的目標是在保持簇數目不變的情況下提高簇的質量。

接下來要討論的是利用簇劃分技術得到更好的聚類效果——二分K-均值演算法



相關文章