Kmeans聚類
kmeans
K-means算法是很典型的基于距離的聚類算法,采用距離作為相似性的評價指標,即認為兩個對象的距離越近,其相似度就越大。該算法認為簇是由距離靠近的對象組成的,因此把得到緊湊且獨立的簇作為最終目標。動圖來源.
k個初始類聚類中心點的選取對聚類結果具有較大的影響,因為在該算法第一步中是隨機的選取任意k個對象作為初始聚類的中心,初始地代表一個簇。該算法在每次迭代中對數據集中剩余的每個對象,根據其與各個簇中心的距離將每個對象重新賦給最近的簇。當考察完所有數據對象后,一次迭代運算完成,新的聚類中心被計算出來。如果在一次迭代前后,J的值沒有發生變化,說明算法已經收斂。
算法步驟:
- 創建k個點作為起始支點(隨機選擇)
- 當任意一個簇的分配結果發生改變的時候
- 對數據集的每個數據點
- 對每個質心
- 計算質心與數據點之間的距離
- 將數據分配到距離其最近的簇
- 對每個質心
- 對每一簇,計算簇中所有點的均值并將其均值作為質心
iris
我們用非常著名的iris數據集。
from sklearn import datasets
iris = datasets.load_iris()
X, y = iris.data, iris.target
data = X[:,[1,3]] # 為了便于可視化,只取兩個維度
plt.scatter(data[:,0],data[:,1]);
[圖片上傳失敗...(image-7972e8-1569669245838)]
歐式距離
計算歐式距離,我們需要為每個點找到離其最近的質心,需要用這個輔助函數。
def distance(p1,p2):
"""
Return Eclud distance between two points.
p1 = np.array([0,0]), p2 = np.array([1,1]) => 1.414
"""
tmp = np.sum((p1-p2)**2)
return np.sqrt(tmp)
distance(np.array([0,0]),np.array([1,1]))
1.4142135623730951
隨機質心
在給定數據范圍內隨機產生k個簇心,作為初始的簇。隨機數都在給定數據的范圍之內dmin + (dmax - dmin) * np.random.rand(k)
實現。
def rand_center(data,k):
"""Generate k center within the range of data set."""
n = data.shape[1] # features
centroids = np.zeros((k,n)) # init with (0,0)....
for i in range(n):
dmin, dmax = np.min(data[:,i]), np.max(data[:,i])
centroids[:,i] = dmin + (dmax - dmin) * np.random.rand(k)
return centroids
centroids = rand_center(data,2)
centroids
array([[ 2.15198267, 2.42476808],
[ 2.77985426, 0.57839675]])
k均值聚類
這個基本的算法只需要明白兩點。
- 給定一組質心,則簇更新,所有的點被分配到離其最近的質心中。
- 給定k簇,則質心更新,所有的質心用其簇的均值替換
當簇不在有更新的時候,迭代停止。當然kmeans有個缺點,就是可能陷入局部最小值,有改進的方法,比如二分k均值,當然也可以多計算幾次,去效果好的結果。
def kmeans(data,k=2):
def _distance(p1,p2):
"""
Return Eclud distance between two points.
p1 = np.array([0,0]), p2 = np.array([1,1]) => 1.414
"""
tmp = np.sum((p1-p2)**2)
return np.sqrt(tmp)
def _rand_center(data,k):
"""Generate k center within the range of data set."""
n = data.shape[1] # features
centroids = np.zeros((k,n)) # init with (0,0)....
for i in range(n):
dmin, dmax = np.min(data[:,i]), np.max(data[:,i])
centroids[:,i] = dmin + (dmax - dmin) * np.random.rand(k)
return centroids
def _converged(centroids1, centroids2):
# if centroids not changed, we say 'converged'
set1 = set([tuple(c) for c in centroids1])
set2 = set([tuple(c) for c in centroids2])
return (set1 == set2)
n = data.shape[0] # number of entries
centroids = _rand_center(data,k)
label = np.zeros(n,dtype=np.int) # track the nearest centroid
assement = np.zeros(n) # for the assement of our model
converged = False
while not converged:
old_centroids = np.copy(centroids)
for i in range(n):
# determine the nearest centroid and track it with label
min_dist, min_index = np.inf, -1
for j in range(k):
dist = _distance(data[i],centroids[j])
if dist < min_dist:
min_dist, min_index = dist, j
label[i] = j
assement[i] = _distance(data[i],centroids[label[i]])**2
# update centroid
for m in range(k):
centroids[m] = np.mean(data[label==m],axis=0)
converged = _converged(old_centroids,centroids)
return centroids, label, np.sum(assement)
由于算法可能局部收斂的問題,隨機多運行幾次,取最優值
best_assement = np.inf
best_centroids = None
best_label = None
for i in range(10):
centroids, label, assement = kmeans(data,2)
if assement < best_assement:
best_assement = assement
best_centroids = centroids
best_label = label
data0 = data[best_label==0]
data1 = data[best_label==1]
如下圖,我們把數據分為兩簇,綠色的點是每個簇的質心,從圖示效果看,聚類效果還不錯。
fig, (ax1,ax2) = plt.subplots(1,2,figsize=(12,5))
ax1.scatter(data[:,0],data[:,1],c='c',s=30,marker='o')
ax2.scatter(data0[:,0],data0[:,1],c='r')
ax2.scatter(data1[:,0],data1[:,1],c='c')
ax2.scatter(centroids[:,0],centroids[:,1],c='b',s=120,marker='o')
plt.show()
[圖片上傳失敗...(image-8a5a3-1569669245838)]