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,2)))
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,1)*disc).flatten() #爲i列特徵所有k個聚簇中心賦值 rand(k,1)表示產生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,2)) #建立各個樣本點的分類和位置信息 第一個表示屬於哪個聚簇,第二個表示距離該聚簇的位置 changeFlag = True #設置標識符,表示是否有聚簇中心改變 while changeFlag: changeFlag = False #開始計算各個點到聚簇中心距離,進行點集合分類 for i in range(m): #對於每個樣本點,判斷是屬於哪個聚簇 bestMinIdx = -1 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,0] != bestMinIdx: #該樣本點有改變聚簇中心 changeFlag = True clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類信息,進行聚簇中心從新分配 for i in range(k): centroids[i] = np.mean(data_X[np.where(clusterAssment[:,0]==i)],0) return centroids,clusterAssment
data_X = loadDataSet("testSet.txt") centroids,clusterAssment = kMeans(data_X,4) plt.figure() plt.scatter(data_X[:,0].flatten(),data_X[:,1].flatten(),c="b",marker="o") plt.scatter(centroids[:,0].flatten(),centroids[:,1].flatten(),c='r',marker="+") plt.show()
咱們能夠發現,在通過屢次測試後,會出現聚簇收斂到局部最小值。致使不能獲得咱們想要的聚簇結果!!!html
http://www.javashuo.com/article/p-yrojlrec-mx.html算法
理解思路便可,實現不必,由於後面的二分K-均值算法更加好。這裏的思路能夠用到二分K-均值算法中。app
經過SSE指標(偏差平方和)來度量聚類效果,是根據各個樣本點到對應聚簇中心聚類來計算的。SSE越小表示數據點越接近質心,聚類效果越好。dom
一種好的方法是經過增長聚簇中心(是將具備最大SSE值的簇劃分爲兩個簇)來減小SSE值,可是違背了K-均值思想(自行增長了聚簇數量),可是咱們能夠在後面進行處理,合併兩個最接近的聚簇中心,從而達到保持聚簇中心數量不變,可是下降SSE值的狀況,獲取全局最優聚簇中心。ide
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,2))) 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,1)*disc).flatten() #爲i列特徵所有k個聚簇中心賦值 rand(k,1)表示產生k行1列在0-1之間的隨機數 return centroids def getSSE(clusterAssment): #傳入一個聚簇中心和對應的距離數據集 return np.sum(clusterAssment[:,1]) 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,2)) #建立各個樣本點的分類和位置信息 第一個表示屬於哪個聚簇,第二個表示距離該聚簇的位置 changeFlag = True #設置標識符,表示是否有聚簇中心改變 while changeFlag: changeFlag = False #開始計算各個點到聚簇中心距離,進行點集合分類 for i in range(m): #對於每個樣本點,判斷是屬於哪個聚簇 bestMinIdx = -1 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,0] != bestMinIdx: #該樣本點有改變聚簇中心 changeFlag = True clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類信息,進行聚簇中心從新分配 for i in range(k): centroids[i] = np.mean(data_X[np.where(clusterAssment[:,0]==i)],0) midCentroids = centroids #進行後處理 if divide == True: # 開始進行一次後處理 maxSSE = 0 maxIdx = -1 for i in range(k): #先找到最大的那個簇,進行劃分 curSSE = getSSE(clusterAssment[np.where(clusterAssment[:,0]==i)]) if curSSE > maxSSE: maxSSE = curSSE maxIdx = i #將最大簇劃分爲兩個簇 temp,new_centroids,new_clusterAssment = kMeans(data_X[np.where(clusterAssment[:,0]==maxIdx)],2) centroids[maxIdx] = new_centroids[0] #更新一個 centroids = np.r_[centroids,np.array([new_centroids[1]])] #更新第二個 new_clusterAssment[0,:] = maxIdx new_clusterAssment[1,:] = centroids.shape[0] - 1 #找的最近的兩個聚簇中心進行合併 clusterAssment[np.where(clusterAssment[:,0]==maxIdx)] = new_clusterAssment #距離更新 distArr = np.zeros((k+1,k+1)) for i in range(k+1): temp_disc = np.sum(np.power(centroids[i] - centroids,2),1) #獲取L2範式距離平方 temp_disc[i] = np.inf #將對角線的0值設置爲無窮大,方便後面求取最小值 distArr[i] = temp_disc #獲取最小距離位置 idx = np.argmin(distArr) cluidx = int((idx) / (k+1)),(idx) % (k+1) #獲取行列索引 #計算兩個聚簇的新的聚簇中心 new_centroids = np.mean(data_X[np.where((clusterAssment[:, 0] == cluidx[1]) | (clusterAssment[:, 0] == cluidx[0]))], 0) centroids = np.delete(centroids,list(cluidx),0) 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,4,divide=True) midCentroids[:, 1] += 0.1 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="+") plt.scatter(centroids[:,0].flatten(),centroids[:,1].flatten(),c='r',marker="+") plt.show()
def getSSE(clusterAssment): #傳入一個聚簇中心和對應的距離數據集 return np.sum(clusterAssment[:,1])
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,2)) #建立各個樣本點的分類和位置信息 第一個表示屬於哪個聚簇,第二個表示距離該聚簇的位置 changeFlag = True #設置標識符,表示是否有聚簇中心改變 while changeFlag: changeFlag = False #開始計算各個點到聚簇中心距離,進行點集合分類 for i in range(m): #對於每個樣本點,判斷是屬於哪個聚簇 bestMinIdx = -1 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,0] != bestMinIdx: #該樣本點有改變聚簇中心 changeFlag = True clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類信息,進行聚簇中心從新分配 for i in range(k): centroids[i] = np.mean(data_X[np.where(clusterAssment[:,0]==i)],0) midCentroids = centroids #進行後處理 if divide == True: # 開始進行一次後處理 maxSSE = 0 maxIdx = -1 for i in range(k): #先找到最大的那個簇,進行劃分 curSSE = getSSE(clusterAssment[np.where(clusterAssment[:,0]==i)]) if curSSE > maxSSE: maxSSE = curSSE maxIdx = i #將最大簇劃分爲兩個簇 temp,new_centroids,new_clusterAssment = kMeans(data_X[np.where(clusterAssment[:,0]==maxIdx)],2) centroids[maxIdx] = new_centroids[0] #更新一個 centroids = np.r_[centroids,np.array([new_centroids[1]])] #更新第二個 new_clusterAssment[0,:] = maxIdx new_clusterAssment[1,:] = centroids.shape[0] - 1 #找的最近的兩個聚簇中心進行合併 clusterAssment[np.where(clusterAssment[:,0]==maxIdx)] = new_clusterAssment #距離更新 distArr = np.zeros((k+1,k+1)) for i in range(k+1): temp_disc = np.sum(np.power(centroids[i] - centroids,2),1) #獲取L2範式距離平方 temp_disc[i] = np.inf #將對角線的0值設置爲無窮大,方便後面求取最小值 distArr[i] = temp_disc #獲取最小距離位置 idx = np.argmin(distArr) cluidx = int((idx) / (k+1)),(idx) % (k+1) #獲取行列索引 #計算兩個聚簇的新的聚簇中心 new_centroids = np.mean(data_X[np.where((clusterAssment[:, 0] == cluidx[1]) | (clusterAssment[:, 0] == cluidx[0]))], 0) centroids = np.delete(centroids,list(cluidx),0) centroids = np.r_[centroids,np.array([new_centroids])] return midCentroids,centroids,clusterAssment #第一個返回的是正常k-均值聚簇結果,第2、三返回的是後處理之後的聚簇中心和樣本分類信息
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-均值算法是在聚簇時,直接考慮了SSE來進行劃分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,2))) 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,1)*disc).flatten() #爲i列特徵所有k個聚簇中心賦值 rand(k,1)表示產生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,2)) #建立各個樣本點的分類和位置信息 第一個表示屬於哪個聚簇,第二個表示距離該聚簇的位置 changeFlag = True #設置標識符,表示是否有聚簇中心改變 while changeFlag: changeFlag = False #開始計算各個點到聚簇中心距離,進行點集合分類 for i in range(m): #對於每個樣本點,判斷是屬於哪個聚簇 bestMinIdx = -1 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,0] != bestMinIdx: #該樣本點有改變聚簇中心 changeFlag = True clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類信息,進行聚簇中心從新分配 for i in range(k): centroids[i] = np.mean(data_X[np.where(clusterAssment[:,0]==i)],0) return centroids,clusterAssment def binkMeans(data_X,k,distCalc=distEclud): #實現二分-k均值算法,開始都是屬於一個聚簇,當咱們聚簇中心數爲K時,退出 m,n = data_X.shape clusterAssment = np.zeros((m,2)) centroid0 = np.mean(data_X,0).tolist() #所有數據集屬於一個聚簇時,設置中心爲均值便可 centList = [centroid0] #用於統計全部的聚簇中心 for i in range(m): clusterAssment[i,1] = 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[:, 0] == i)] #進行簇劃分 splitCentroids,splitClustArr = kMeans(splitClusData,2,distCalc) #獲取所有SSE值 splitSSE = np.sum(splitClustArr[:,1]) noSplitSSE = np.sum(clusterAssment[np.where(clusterAssment[:, 0] != i),1]) 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[0].tolist() centList.append(bestSplitCent[1].tolist()) #還要對劃分的數據集進行標籤更新 clusterAssment[np.where(clusterAssment[:,0]==bestSplitClus)[0],:] = bestSplitClu return np.array(centList),clusterAssment data_X = loadDataSet("testSet2.txt") centroids,clusterAssment = binkMeans(data_X,3) plt.figure() plt.scatter(data_X[:,0].flatten(),data_X[:,1].flatten(),c="b",marker="o") print(centroids) plt.scatter(centroids[:,0].reshape(1,3).tolist()[0],centroids[:,1].reshape(1,3).tolist()[0],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,2))) 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,1)*disc).flatten() #爲i列特徵所有k個聚簇中心賦值 rand(k,1)表示產生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,2)) #建立各個樣本點的分類和位置信息 第一個表示屬於哪個聚簇,第二個表示距離該聚簇的位置 changeFlag = True #設置標識符,表示是否有聚簇中心改變 while changeFlag: changeFlag = False #開始計算各個點到聚簇中心距離,進行點集合分類 for i in range(m): #對於每個樣本點,判斷是屬於哪個聚簇 bestMinIdx = -1 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,0] != bestMinIdx: #該樣本點有改變聚簇中心 changeFlag = True clusterAssment[i,:] = bestMinIdx,bestMinDist #開始根據上面樣本點分類信息,進行聚簇中心從新分配 for i in range(k): centroids[i] = np.mean(data_X[np.where(clusterAssment[:,0]==i)],0) return centroids,clusterAssment
def binkMeans(data_X,k,distCalc=distEclud): #實現二分-k均值算法,開始都是屬於一個聚簇,當咱們聚簇中心數爲K時,退出 m,n = data_X.shape clusterAssment = np.zeros((m,2)) centroid0 = np.mean(data_X,0).tolist() #所有數據集屬於一個聚簇時,設置中心爲均值便可 centList = [centroid0] #用於統計全部的聚簇中心 for i in range(m): clusterAssment[i,1] = 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[:, 0] == i)] #進行簇劃分 splitCentroids,splitClustArr = kMeans(splitClusData,2,distCalc) #獲取所有SSE值 splitSSE = np.sum(splitClustArr[:,1]) noSplitSSE = np.sum(clusterAssment[np.where(clusterAssment[:, 0] != i),1]) 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[0].tolist() centList.append(bestSplitCent[1].tolist()) #還要對劃分的數據集進行標籤更新 clusterAssment[np.where(clusterAssment[:,0]==bestSplitClus)[0],:] = bestSplitClu return np.array(centList),clusterAssment
data_X = loadDataSet("testSet2.txt") centroids,clusterAssment = binkMeans(data_X,3) plt.figure() plt.scatter(data_X[:,0].flatten(),data_X[:,1].flatten(),c="b",marker="o") print(centroids) plt.scatter(centroids[:,0].reshape(1,3).tolist()[0],centroids[:,1].reshape(1,3).tolist()[0],c='r',marker="+") plt.show()