1. 程式人生 > 實用技巧 >機器學習實戰---K均值聚類演演算法

機器學習實戰---K均值聚類演演算法

一:一般K均值聚類演演算法實現

(一)匯入資料

import numpy as np
import matplotlib.pyplot as plt def loadDataSet(filename):
dataSet = np.loadtxt(filename)
return dataSet

(二)計算兩個向量之間的距離

def distEclud(vecA,vecB):   #計算兩個向量之間距離
return np.sqrt(np.sum(np.power(vecA-vecB,)))

(三)隨機初始化聚簇中心

def randCent(data_X,k):    #隨機初始化聚簇中心 可以隨機選取樣本點,或者選取距離隨機
m,n = data_X.shape
centroids = np.zeros((k,n))
#開始隨機初始化
for i in range(n):
Xmin = np.min(data_X[:,i]) #獲取該特徵最小值
Xmax = np.max(data_X[:,i]) #獲取該特徵最大值 disc = Xmax - Xmin #獲取差值
centroids[:,i] = (Xmin + np.random.rand(k,)*disc).flatten() #為i列特徵全部k個聚簇中心賦值 rand(k,)表示產生k行1列在0-1之間的隨機數
return centroids

(四)實現聚簇演演算法

def kMeans(data_X,k,distCalc=distEclud,createCent=randCent):   #實現k均值演演算法,當所有中心不再改變時退出
m,n = data_X.shape
centroids = createCent(data_X,k) #建立隨機聚簇中心
clusterAssment = np.zeros((m,)) #建立各個樣本點的分類和位置資訊 第一個表示屬於哪一個聚簇,第二個表示距離該聚簇的位置
changeFlag = True #設定識別符號,表示是否有聚簇中心改變 while changeFlag:
changeFlag = False
#開始計算各個點到聚簇中心距離,進行點集合分類
for i in range(m): #對於每一個樣本點,判斷是屬於哪一個聚簇
bestMinIdx = -
bestMinDist = np.inf
for j in range(k): #求取到各個聚簇中心的距離
dist = distCalc(centroids[j], data_X[i])
if dist < bestMinDist:
bestMinIdx = j
bestMinDist = dist
if clusterAssment[i,] != bestMinIdx: #該樣本點有改變聚簇中心
changeFlag = True
clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類資訊,進行聚簇中心重新分配
for i in range(k):
centroids[i] = np.mean(data_X[np.where(clusterAssment[:,]==i)],) return centroids,clusterAssment

(五)結果測試

data_X = loadDataSet("testSet.txt")
centroids,clusterAssment = kMeans(data_X,) plt.figure()
plt.scatter(data_X[:,].flatten(),data_X[:,].flatten(),c="b",marker="o")
plt.scatter(centroids[:,].flatten(),centroids[:,].flatten(),c='r',marker="+")
plt.show()

我們可以發現,在經過多次測試後,會出現聚簇收斂到區域性最小值。導致不能得到我們想要的聚簇結果!!!

二:多次測試,計算代價,選取最優聚簇中心

https://www.cnblogs.com/ssyfj/p/12966305.html

避免區域性最優:如果想讓找到最優可能的聚類,可以嘗試多次隨機初始化,以此來保證能夠得到一個足夠好的結果,選取代價最小的一個也就是代價函式J最小的。事實證明,在聚類數K較小的情況下(2~10個),使用多次隨機初始化會有較大的影響,而如果K很大的情況,多次隨機初始化可能並不會有太大效果

三:後處理提高聚類效能(可以不實現)

理解思路即可,實現沒必要,因為後面的二分K-均值演演算法更加好。這裡的思路可以用到二分K-均值演演算法中。

通過SSE指標(誤差平方和)來度量聚類效果,是根據各個樣本點到對應聚簇中心聚類來計算的。SSE越小表示資料點越接近質心,聚類效果越好。

一種好的方法是通過增加聚簇中心(是將具有最大SSE值的簇劃分為兩個簇)來減少SSE值,但是違背了K-均值思想(自行增加了聚簇數量),但是我們可以在後面進行處理,合併兩個最接近的聚簇中心,從而達到保持聚簇中心數量不變,但是降低SSE值的情況,獲取全域性最優聚簇中心。

(一)全部程式碼

import numpy as np
import matplotlib.pyplot as plt def loadDataSet(filename):
dataSet = np.loadtxt(filename)
return dataSet def distEclud(vecA,vecB): #計算兩個向量之間距離
return np.sqrt(np.sum(np.power(vecA-vecB,))) def randCent(data_X,k): #隨機初始化聚簇中心 可以隨機選取樣本點,或者選取距離隨機
m,n = data_X.shape
centroids = np.zeros((k,n))
#開始隨機初始化
for i in range(n):
Xmin = np.min(data_X[:,i]) #獲取該特徵最小值
Xmax = np.max(data_X[:,i]) #獲取該特徵最大值 disc = Xmax - Xmin #獲取差值
centroids[:,i] = (Xmin + np.random.rand(k,)*disc).flatten() #為i列特徵全部k個聚簇中心賦值 rand(k,)表示產生k行1列在0-1之間的隨機數
return centroids def getSSE(clusterAssment): #傳入一個聚簇中心和對應的距離資料集
return np.sum(clusterAssment[:,]) def kMeans(data_X,k,distCalc=distEclud,createCent=randCent,divide=False): #實現k均值演演算法,當所有中心不再改變時退出
m,n = data_X.shape
centroids = createCent(data_X,k) #建立隨機聚簇中心
clusterAssment = np.zeros((m,)) #建立各個樣本點的分類和位置資訊 第一個表示屬於哪一個聚簇,第二個表示距離該聚簇的位置
changeFlag = True #設定識別符號,表示是否有聚簇中心改變 while changeFlag:
changeFlag = False
#開始計算各個點到聚簇中心距離,進行點集合分類
for i in range(m): #對於每一個樣本點,判斷是屬於哪一個聚簇
bestMinIdx = -
bestMinDist = np.inf
for j in range(k): #求取到各個聚簇中心的距離
dist = distCalc(centroids[j], data_X[i])
if dist < bestMinDist:
bestMinIdx = j
bestMinDist = dist
if clusterAssment[i,] != bestMinIdx: #該樣本點有改變聚簇中心
changeFlag = True
clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類資訊,進行聚簇中心重新分配
for i in range(k):
centroids[i] = np.mean(data_X[np.where(clusterAssment[:,]==i)],) midCentroids = centroids #進行後處理
if divide == True: # 開始進行一次後處理
maxSSE =
maxIdx = -
for i in range(k): #先找到最大的那個簇,進行劃分
curSSE = getSSE(clusterAssment[np.where(clusterAssment[:,]==i)])
if curSSE > maxSSE:
maxSSE = curSSE
maxIdx = i #將最大簇劃分為兩個簇
temp,new_centroids,new_clusterAssment = kMeans(data_X[np.where(clusterAssment[:,]==maxIdx)],)
centroids[maxIdx] = new_centroids[] #更新一個
centroids = np.r_[centroids,np.array([new_centroids[]])] #更新第二個
new_clusterAssment[,:] = maxIdx
new_clusterAssment[,:] = centroids.shape[] - #找的最近的兩個聚簇中心進行合併
clusterAssment[np.where(clusterAssment[:,]==maxIdx)] = new_clusterAssment #距離更新
distArr = np.zeros((k+,k+))
for i in range(k+):
temp_disc = np.sum(np.power(centroids[i] - centroids,),) #獲取L2正規化距離平方
temp_disc[i] = np.inf #將對角線的0值設定為無窮大,方便後面求取最小值
distArr[i] = temp_disc #獲取最小距離位置
idx = np.argmin(distArr)
cluidx = int((idx) / (k+)),(idx) % (k+) #獲取行列索引
#計算兩個聚簇的新的聚簇中心
new_centroids = np.mean(data_X[np.where((clusterAssment[:, ] == cluidx[]) | (clusterAssment[:, ] == cluidx[]))], )
centroids = np.delete(centroids,list(cluidx),) centroids = np.r_[centroids,np.array([new_centroids])] return midCentroids,centroids,clusterAssment plt.figure() data_X = loadDataSet("testSet.txt") midCentroids,centroids,clusterAssment = kMeans(data_X,,divide=True)
midCentroids[:, ] += 0.1
plt.scatter(data_X[:,].flatten(),data_X[:,].flatten(),c="b",marker="o")
plt.scatter(midCentroids[:, ].flatten(), midCentroids[:, ].flatten(), c='g', marker="+")
plt.scatter(centroids[:,].flatten(),centroids[:,].flatten(),c='r',marker="+")
plt.show()

(二)計算SSE函式

def getSSE(clusterAssment):    #傳入一個聚簇中心和對應的距離資料集
return np.sum(clusterAssment[:,])

(三)修改原有的k-均值演演算法

def kMeans(data_X,k,distCalc=distEclud,createCent=randCent,divide=False):   #實現k均值演演算法,當所有中心不再改變時退出  最後一個引數,用來表示是否是後處理,True不需要後處理
m,n = data_X.shape
centroids = createCent(data_X,k) #建立隨機聚簇中心
clusterAssment = np.zeros((m,)) #建立各個樣本點的分類和位置資訊 第一個表示屬於哪一個聚簇,第二個表示距離該聚簇的位置
changeFlag = True #設定識別符號,表示是否有聚簇中心改變 while changeFlag:
changeFlag = False
#開始計算各個點到聚簇中心距離,進行點集合分類
for i in range(m): #對於每一個樣本點,判斷是屬於哪一個聚簇
bestMinIdx = -
bestMinDist = np.inf
for j in range(k): #求取到各個聚簇中心的距離
dist = distCalc(centroids[j], data_X[i])
if dist < bestMinDist:
bestMinIdx = j
bestMinDist = dist
if clusterAssment[i,] != bestMinIdx: #該樣本點有改變聚簇中心
changeFlag = True
clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類資訊,進行聚簇中心重新分配
for i in range(k):
centroids[i] = np.mean(data_X[np.where(clusterAssment[:,]==i)],) midCentroids = centroids #進行後處理
if divide == True: # 開始進行一次後處理
maxSSE =
maxIdx = -
for i in range(k): #先找到最大的那個簇,進行劃分
curSSE = getSSE(clusterAssment[np.where(clusterAssment[:,]==i)])
if curSSE > maxSSE:
maxSSE = curSSE
maxIdx = i #將最大簇劃分為兩個簇
temp,new_centroids,new_clusterAssment = kMeans(data_X[np.where(clusterAssment[:,]==maxIdx)],)
centroids[maxIdx] = new_centroids[] #更新一個
centroids = np.r_[centroids,np.array([new_centroids[]])] #更新第二個
new_clusterAssment[,:] = maxIdx
new_clusterAssment[,:] = centroids.shape[] - #找的最近的兩個聚簇中心進行合併
clusterAssment[np.where(clusterAssment[:,]==maxIdx)] = new_clusterAssment #距離更新
distArr = np.zeros((k+,k+))
for i in range(k+):
temp_disc = np.sum(np.power(centroids[i] - centroids,),) #獲取L2正規化距離平方
temp_disc[i] = np.inf #將對角線的0值設定為無窮大,方便後面求取最小值
distArr[i] = temp_disc #獲取最小距離位置
idx = np.argmin(distArr)
cluidx = int((idx) / (k+)),(idx) % (k+) #獲取行列索引
#計算兩個聚簇的新的聚簇中心
new_centroids = np.mean(data_X[np.where((clusterAssment[:, ] == cluidx[]) | (clusterAssment[:, ] == cluidx[]))], )
centroids = np.delete(centroids,list(cluidx),) centroids = np.r_[centroids,np.array([new_centroids])] return midCentroids,centroids,clusterAssment  #第一個返回的是正常k-均值聚簇結果,第二、三返回的是後處理以後的聚簇中心和樣本分類資訊

(四)測試現象

plt.figure()

data_X = loadDataSet("testSet.txt")

midCentroids,centroids,clusterAssment = kMeans(data_X,4,divide=True)
midCentroids[:, 1] += 0.1  #將兩個K-均值處理結果錯開
plt.scatter(data_X[:,0].flatten(),data_X[:,1].flatten(),c="b",marker="o")  #原始資料
plt.scatter(midCentroids[:, 0].flatten(), midCentroids[:, 1].flatten(), c='g', marker="+")  #一般K-均值聚簇
plt.scatter(centroids[:,0].flatten(),centroids[:,1].flatten(),c='r',marker="+"
)  #後處理以後的聚簇現象
plt.show()

注意:紅色為後處理結果、綠色為一般K-均值處理

可以看到從左到右,後處理現象依次顯現,尤其是最右邊圖中,後處理對原始聚簇劃分進行了很大的改進!!!

雖然後處理不錯,但是後面的二分K-均值演演算法是在聚簇時,直接考慮了SSE來進行劃分K個聚簇,而不是在聚簇後進行考慮再進行劃分合併。所以下面來看二分K-均值演演算法

四:二分K-均值演演算法

(一)全部程式碼

import numpy as np
from numpy import *
import matplotlib.pyplot as plt def loadDataSet(filename):
dataSet = np.loadtxt(filename)
return dataSet def distEclud(vecA,vecB): #計算兩個向量之間距離
return np.sqrt(np.sum(np.power(vecA-vecB,))) def randCent(data_X,k): #隨機初始化聚簇中心 可以隨機選取樣本點,或者選取距離隨機
m,n = data_X.shape
centroids = np.zeros((k,n))
#開始隨機初始化
for i in range(n):
Xmin = np.min(data_X[:,i]) #獲取該特徵最小值
Xmax = np.max(data_X[:,i]) #獲取該特徵最大值
disc = Xmax - Xmin #獲取差值
centroids[:,i] = (Xmin + np.random.rand(k,)*disc).flatten() #為i列特徵全部k個聚簇中心賦值 rand(k,)表示產生k行1列在0-1之間的隨機數
return centroids def kMeans(data_X,k,distCalc=distEclud,createCent=randCent): #實現k均值演演算法,當所有中心不再改變時退出
m,n = data_X.shape
centroids = createCent(data_X,k) #建立隨機聚簇中心
clusterAssment = np.zeros((m,)) #建立各個樣本點的分類和位置資訊 第一個表示屬於哪一個聚簇,第二個表示距離該聚簇的位置
changeFlag = True #設定識別符號,表示是否有聚簇中心改變 while changeFlag:
changeFlag = False
#開始計算各個點到聚簇中心距離,進行點集合分類
for i in range(m): #對於每一個樣本點,判斷是屬於哪一個聚簇
bestMinIdx = -
bestMinDist = np.inf
for j in range(k): #求取到各個聚簇中心的距離
dist = distCalc(centroids[j], data_X[i])
if dist < bestMinDist:
bestMinIdx = j
bestMinDist = dist
if clusterAssment[i,] != bestMinIdx: #該樣本點有改變聚簇中心
changeFlag = True
clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類資訊,進行聚簇中心重新分配
for i in range(k):
centroids[i] = np.mean(data_X[np.where(clusterAssment[:,]==i)],) return centroids,clusterAssment def binkMeans(data_X,k,distCalc=distEclud): #實現二分-k均值演演算法,開始都是屬於一個聚簇,當我們聚簇中心數為K時,退出
m,n = data_X.shape
clusterAssment = np.zeros((m,))
centroid0 = np.mean(data_X,).tolist() #全部資料集屬於一個聚簇時,設定中心為均值即可 centList = [centroid0] #用於統計所有的聚簇中心
for i in range(m):
clusterAssment[i,] = distCalc(data_X[i],centroid0)#設定距離,前面初始為0,設定了聚簇中心類別 while len(centList) < k: #不滿足K個聚簇,則一直進行分類
lowestSSE = np.inf #用於計算每個聚簇的SSE值
for i in range(len(centList)): #嘗試對每一個聚簇進行一次劃分,看哪一個簇劃分後所有簇的SSE最小
#先對該簇進行劃分,然後獲取劃分後的SSE值,和沒有進行劃分的資料集的SSE值
#先進行資料劃分
splitClusData = data_X[np.where(clusterAssment[:, ] == i)]
#進行簇劃分
splitCentroids,splitClustArr = kMeans(splitClusData,,distCalc)
#獲取全部SSE值
splitSSE = np.sum(splitClustArr[:,])
noSplitSSE = np.sum(clusterAssment[np.where(clusterAssment[:, ] != i),])
if (splitSSE + noSplitSSE) < lowestSSE:
lowestSSE = splitSSE + noSplitSSE
bestSplitClus = i #記錄劃分資訊
bestSplitCent = splitCentroids
bestSplitClu = splitClustArr.copy()
#更新簇的分配結果 二分後資料集:對於索引0,則保持原有的i位置,對於索引1則加到列表後面
bestSplitClu[np.where(bestSplitClu[:,]==)[],] = len(centList)
bestSplitClu[np.where(bestSplitClu[:,]==)[],] = bestSplitClus #還要繼續更新聚簇中心
centList[bestSplitClus] = bestSplitCent[].tolist()
centList.append(bestSplitCent[].tolist()) #還要對劃分的資料集進行標籤更新
clusterAssment[np.where(clusterAssment[:,]==bestSplitClus)[],:] = bestSplitClu return np.array(centList),clusterAssment data_X = loadDataSet("testSet2.txt")
centroids,clusterAssment = binkMeans(data_X,)
plt.figure()
plt.scatter(data_X[:,].flatten(),data_X[:,].flatten(),c="b",marker="o")
print(centroids) plt.scatter(centroids[:,].reshape(,).tolist()[],centroids[:,].reshape(,).tolist()[],c='r',marker="+")
plt.show()

(二)不變程式碼

import numpy as np
from numpy import *
import matplotlib.pyplot as plt def loadDataSet(filename):
dataSet = np.loadtxt(filename)
return dataSet def distEclud(vecA,vecB): #計算兩個向量之間距離
return np.sqrt(np.sum(np.power(vecA-vecB,))) def randCent(data_X,k): #隨機初始化聚簇中心 可以隨機選取樣本點,或者選取距離隨機
m,n = data_X.shape
centroids = np.zeros((k,n))
#開始隨機初始化
for i in range(n):
Xmin = np.min(data_X[:,i]) #獲取該特徵最小值
Xmax = np.max(data_X[:,i]) #獲取該特徵最大值
disc = Xmax - Xmin #獲取差值
centroids[:,i] = (Xmin + np.random.rand(k,)*disc).flatten() #為i列特徵全部k個聚簇中心賦值 rand(k,)表示產生k行1列在0-1之間的隨機數
return centroids def kMeans(data_X,k,distCalc=distEclud,createCent=randCent): #實現k均值演演算法,當所有中心不再改變時退出
m,n = data_X.shape
centroids = createCent(data_X,k) #建立隨機聚簇中心
clusterAssment = np.zeros((m,)) #建立各個樣本點的分類和位置資訊 第一個表示屬於哪一個聚簇,第二個表示距離該聚簇的位置
changeFlag = True #設定識別符號,表示是否有聚簇中心改變 while changeFlag:
changeFlag = False
#開始計算各個點到聚簇中心距離,進行點集合分類
for i in range(m): #對於每一個樣本點,判斷是屬於哪一個聚簇
bestMinIdx = -
bestMinDist = np.inf
for j in range(k): #求取到各個聚簇中心的距離
dist = distCalc(centroids[j], data_X[i])
if dist < bestMinDist:
bestMinIdx = j
bestMinDist = dist
if clusterAssment[i,] != bestMinIdx: #該樣本點有改變聚簇中心
changeFlag = True
clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類資訊,進行聚簇中心重新分配
for i in range(k):
centroids[i] = np.mean(data_X[np.where(clusterAssment[:,]==i)],) return centroids,clusterAssment

(三)二分K-均值實現

def binkMeans(data_X,k,distCalc=distEclud):   #實現二分-k均值演演算法,開始都是屬於一個聚簇,當我們聚簇中心數為K時,退出
m,n = data_X.shape
clusterAssment = np.zeros((m,))
centroid0 = np.mean(data_X,).tolist() #全部資料集屬於一個聚簇時,設定中心為均值即可 centList = [centroid0] #用於統計所有的聚簇中心
for i in range(m):
clusterAssment[i,] = distCalc(data_X[i],centroid0)#設定距離,前面初始為0,設定了聚簇中心類別 while len(centList) < k: #不滿足K個聚簇,則一直進行分類
lowestSSE = np.inf #用於計算每個聚簇的SSE值
for i in range(len(centList)): #嘗試對每一個聚簇進行一次劃分,看哪一個簇劃分後所有簇的SSE最小
#先對該簇進行劃分,然後獲取劃分後的SSE值,和沒有進行劃分的資料集的SSE值
#先進行資料劃分
splitClusData = data_X[np.where(clusterAssment[:, ] == i)]
#進行簇劃分
splitCentroids,splitClustArr = kMeans(splitClusData,,distCalc)
#獲取全部SSE值
splitSSE = np.sum(splitClustArr[:,])
noSplitSSE = np.sum(clusterAssment[np.where(clusterAssment[:, ] != i),])
if (splitSSE + noSplitSSE) < lowestSSE:
lowestSSE = splitSSE + noSplitSSE
bestSplitClus = i #記錄劃分資訊
bestSplitCent = splitCentroids
bestSplitClu = splitClustArr.copy()
#更新簇的分配結果 二分後資料集:對於索引0,則保持原有的i位置,對於索引1則加到列表後面
bestSplitClu[np.
where(bestSplitClu[:,0]==1)[0],0] = len(centList)
bestSplitClu[np.where(bestSplitClu[:,0]==0)[0],0] =
bestSplitClus

#還要繼續更新聚簇中心
centList[bestSplitClus] = bestSplitCent[].tolist()
centList.append(bestSplitCent[].tolist()) #還要對劃分的資料集進行標籤更新
clusterAssment[np.
where(clusterAssment[:,0]==bestSplitClus)[0],:] = bestSplitClu return np.array(centList),clusterAssment

重點:使用np.where查詢時,對資料集列值進行修改時,需要選取np.where()[0]---表示索引位置,之後在資料集中選取列數data[np.where()[0],:]=...

(四)結果測試

data_X = loadDataSet("testSet2.txt")
centroids,clusterAssment = binkMeans(data_X,)
plt.figure()
plt.scatter(data_X[:,].flatten(),data_X[:,].flatten(),c="b",marker="o")
print(centroids) plt.scatter(centroids[:,].reshape(,).tolist()[],centroids[:,].reshape(,).tolist()[],c='r',marker="+")
plt.show()