轉載請註明作者和出處: https://blog.csdn.net/weixin_37392582
代碼地址: https://gitee.com/wuweijun
開發平臺: Win10 + Python3.6 + Anaconda3
編 者: 無尾
一、前言
聚類是一種無監督的學習,它將相似的對象歸到同一簇中。它有點像全自動分類。聚類的方法幾乎可以應用於所有對象,簇內的對象越相似,聚類的效果越好。本章要學習一種稱爲K-均值(K-means)聚類的孫發。之所以稱之爲K-均值,是因爲它可以發現k個不同的簇,且每個簇的中心採用簇中所含值的均值計算而成。
簇識別。簇識別給出聚類結果的含義。假定有一些數據,現在將相似數據歸到一起,簇識別會告訴我們這些簇到底都是些什麼。聚類與分類的最大不同在與,分類的目標實現已知,而聚類則不一樣。不易呀其產生的結果與分類相同,而只是類別沒有預先定義,聚類有時也被稱爲無監督分類(unsupervised)。
二、K-均值聚類算法
優點:容易實現
缺點:可能收斂到局部最小值,在大規模數據集上收斂較慢
適用數據類型:數值型數據
1、工作流程
- 首先隨機確定k個初始點作爲質心
- 爲每個點找到距其最近的質心,並將其分配給該質心所對應的簇(打標籤)
- 將每個質心更新爲該簇所有點的平均值
- 重複前面兩個步驟,知道所有點的簇類不再變化,即確定的質心爲最優,即可完成聚類。
2、僞代碼
創建k個點作爲起始質心(通常是隨機選擇樣本點)
當任意一個點的簇類(簇分配結果)發生改變時
對數據集中的每個數據點
對每個質心
計算質心與數據點之間的距離
將數據點分配到距其最近的簇
對每一個簇,計算簇中所有點的均值並將均值作爲質心
3、流程圖
4、核心代碼解釋
(1)euclidentDistance
計算了各樣本點到各質心距離,用距離差平方和表示,也稱誤差平方和。在主函數中,樣本點會根據距離選取最新的質心作爲簇類。
def euclidentDistance(vector1, vector2): #歐氏距離 Euclidean
"""
Function: 歐式距離計算
Input:兩個座標(x0,x1),(x2, x3)
Output: 兩點的歐式距離
"""
return np.sqrt(np.sum(np.power(vector2 - vector1,2)))
(2)initCentroids
在樣本點中,隨機獲取k個樣本作爲初始質心
random.uniform(x1,x2) _在[x1,x2)內隨機選取一個符合均勻分佈的浮點數
def initCentroids(data, k):
"""
Function: 隨機獲取k個質心
Input:數據集,質心數量
Output:k個質心的集合
"""
numSamples, dim = data.shape
centroids = np.zeros((k,dim))
for i in range(k):
#
index = int(np.random.uniform(0, numSamples))
centroids[i,:] = data[index,:]
return centroids
(3)kmenas
程序的主函數,通過迭代(劃分樣本-更新質心),實現聚類
def kmeans(dataSet, k):
"""
Function:通過迭代,確定質心和樣本類別
Input :數據集,質心數量
Output :質心座標 + [Ki,distance^2]
"""
"""
僞代碼:
當質心還未更新完畢(各樣本點的簇類值不再變化):
1對每一個樣本:
對每一個質心:
計算樣本到質心的距離
選取得到最小距離值時的質心,作爲該樣本的簇類
判斷該簇類是否是更新值,如果是:
將該樣本的簇類和平方誤差進行保存
2由已分好的簇類中取均值,重新確立質心
輸出:質心 + 樣本的簇類標籤、
"""
numSamples = dataSet.shape[0]
#獲取樣本數
clusterAssment = np.mat((np.zeros((numSamples,2))))
clusterChanged = True
#初始化k個質心
centroids = initCentroids(dataSet, k)
#當聚類不在變化時,停止循環
while clusterChanged:
clusterChanged = False
#遍歷所有樣本
for i in range(numSamples):
#對所有質心點,計算樣本點到質心的距離,找到樣本點到
#一質心的最小距離,並取對該樣本點進行劃分
minDist = float('inf')
minIndex = 0
for j in range(k):
#計算點到質心的距離
distance = euclidentDistance(centroids[j,:], dataSet[i,:])
#取距點最近的質心爲當前的簇類2
if distance < minDist:
minDist = distance
minIndex = j
#更新clusterChanged爲True,將簇類和距離的平方保存
if clusterAssment[i,0] != minIndex:
clusterChanged = True
clusterAssment[i,:] = minIndex, minDist**2
#更新質心
for j in range(k):
pointsInCluster = dataSet[np.nonzero(clusterAssment[:,0] == j)[0]]
centroids[j,:] = np.mean(pointsInCluster, axis = 0)
print('完成聚類算法')
return centroids, clusterAssment
三、二分K-均值算法
在K-均值聚類中,點的簇分配結果值沒有那麼準確,K-均值算法收斂但是聚類效果較差。因爲K-均值算法收斂到了局部最小值,而非全局最小值。因爲初始的質心點是隨機選取的,在對質心點優化過程的迭代,也是基於初始質心的位置而進行優化的,這樣的優化結果只是這一局部的最優解,而非全局的。
那麼,我們引入一種用於度量聚類效果的指標:SSE(Sum of Squared Error,誤差平方和)。SSE對應函數euclidentDistance的返回結果,SSE值越小,表明數據點越接近於它們的質心,質心位於數據點的中央,聚類效果也越好。因爲我們對誤差(點到質心的距離)取了平方,因此更加重視那些遠離中心的點。
因此,我們在每次劃分簇的時候,對所有的劃分結果用SSE進行比較,選取SSE值最小的劃分方式進行聚類。這樣避免了本應該劃分的簇沒有被劃分,而點很集中的簇卻被劃分了的情況。
1、工作流程
- 首先將所有點作爲一個簇,然後將該簇一分爲二
- 之後選取其中一個簇繼續劃分,劃分哪一個簇取決於其劃分是否可以最大程度降低SSE值
- 上述基於SSE的劃分過程不斷重複,直到得到指定的簇數目爲止。
2、僞代碼
將所有的點看成一個簇
當簇數目小於k時
對每一個簇
在給定的簇上面進行K-均值聚類(k=2)
計算將該簇一分爲二之後的總誤差(被劃分的簇+沒有被選擇劃分的簇)
選擇使得誤差SSE最小的那個簇的劃分操作執行
3、流程圖
4、核心代碼解釋
def biKmeans(dataSet, k):
"""
Function:不斷二次劃分最小誤差數據集,得到質點和分類
Input :數據集,質心數量
Output :質心座標 + [Ki,distance^2]
"""
"""
僞代碼:
初始化質心爲所有樣本點的均值
計算樣本點到初始質心的歐式距離,clusterAssment
當確定的質心數量不足k個時:
分類標籤索引爲質心的數量- len(centList)
遍歷每個質心:
得到質心類別對應的數據樣本
將這些樣本劃分成2類,得到新的質心點和clusterAssment
計算總體誤差 - 參與劃分的樣本誤差和沒參與劃分的樣本誤差之和
當,當前誤差值爲最小誤差時:
最佳分類標籤(bestCentroidSplit)是當前的質心索引
最佳質心點(bestNewCentroids)是當前對應的質心點
bestClusterAssment爲當前對應的clusterAssment
選擇一個類別的樣本繼續劃分,類別爲 len(cenList),另外一個樣本標籤爲bestCentroidToSplit
更新質心數組,最後一個質點更新爲‘kmeans’中‘0’對應的質點,然後append進一個最新的質點
"""
m = np.shape(dataSet)[0]
clusterAssment = np.mat(np.zeros((m,2)))
#初始質心,爲所有樣本點的均值
centroid = np.mean(dataSet, axis = 0).tolist()[0]
centList = [centroid]
#計算樣本點到質心的距離
for i in range(m):
clusterAssment[i,1] = euclidentDistance(np.mat(centroid), dataSet[i,:]) ** 2
#迭代收斂,分裂出新的質心,退出條件是分裂出的質心數量達到k
while len(centList) < k:
#定義誤差值
minSSE = float('inf')
#看上去是定義長度, 實則爲每個質心打標籤(0,1,2……)
numCurrCluster = len(centList)
#判斷哪個質心好
for i in range(numCurrCluster):
#得到屬於該質心的數據
pointsInCurrCluster = dataSet[np.nonzero(clusterAssment[:,0] == i)[0],:]
#得到劃分成2類的【質心,分類+誤差】
centroids, splitClusterAssment = kmeans(pointsInCurrCluster, 2)
#計算質心劃分後的誤差
splitSSE = sum(splitClusterAssment[:,1])
#計算沒有參與劃分的誤差
notSplitSSE = sum(clusterAssment[np.nonzero(clusterAssment[:,0] != i)[0],1])
currSplitSSE = splitSSE + notSplitSSE
#尋找最小的誤差
if currSplitSSE < minSSE:
minSSE = currSplitSSE
bestCentroidToSplit = i
bestNewCentroids = centroids.copy()
bestClusterAssment = splitClusterAssment.copy()\
#修改集羣索引以添加新集羣, 類別1返回該質心原來的值, 0 更新爲選擇誤差最小的質心再進行劃分
#給予新的分類新的標籤,另外一個分類標籤沿用原來的
bestClusterAssment[np.nonzero(bestClusterAssment[:,0] == 1)[0],[0]] = numCurrCluster
bestClusterAssment[np.nonzero(bestClusterAssment[:,0] == 0)[0],[0]] = bestCentroidToSplit
#更新和添加新的2子簇的質心
centList[bestCentroidToSplit] = bestNewCentroids[0,:]
centList.append(bestNewCentroids[1,:])
#更新集羣被更改的樣本的索引和錯誤 - 在kmens中被貼上‘0’分類,在此處進行恢復
clusterAssment[np.nonzero(clusterAssment[:,0] == bestCentroidToSplit), :] = bestClusterAssment
print('二分k-均值算法完成')
return np.mat(centList), clusterAssment